<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>寒流の编程笔记</title><link>https://blog.coldwind.top/</link><description>Recent content on 寒流の编程笔记</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Tue, 26 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.coldwind.top/index.xml" rel="self" type="application/rss+xml"/><item><title>你好，世界！</title><link>https://blog.coldwind.top/posts/hello-world/</link><pubDate>Sun, 18 Feb 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/hello-world/</guid><description>&lt;img src="https://blog.coldwind.top/posts/hello-world/cover.jpg" alt="Featured image of post 你好，世界！" />&lt;p>欢迎来到我的博客！&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">《CommunityToolkit.Mvvm 社区工具包从入门到精通》系列教程已经基本完工！你可以&lt;a class="link" href="https://mvvm.coldwind.top/" target="_blank" rel="noopener"
>点击这里&lt;/a>查看，并在评论区留下你的宝贵意见。&lt;/div>
&lt;/div>
&lt;p>虽然博客的绝大多数所需功能都已经实现，但仍有一些细节需要微调，比如：&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> 右侧目录宽度太大&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 博客正文的标题下方不需要显示摘要内容&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 添加用户访问统计&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 阅读时长的计算并不十分准确&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> 去掉文章小标题前面的“#”号&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> 添加 RSS 订阅功能&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> 添加一个「关于我」页面&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> 添加评论区&lt;/li>
&lt;/ul>
&lt;p>更多关于我的信息，请查看&lt;a class="link" href="https://blog.coldwind.top/about" >关于我&lt;/a>页面。&lt;/p>
&lt;p>本博客使用 Hugo 搭建，主题是 &lt;a class="link" href="https://github.com/CaiJimmy/hugo-theme-stack" target="_blank" rel="noopener"
>Stack&lt;/a>。&lt;/p>
&lt;p>封面图片使用 &lt;a class="link" href="https://github.com/lllyasviel/Fooocus" target="_blank" rel="noopener"
>Fooocus&lt;/a> 绘制。&lt;/p></description></item><item><title>如何正确使用 WPF 中 Application 的 MainWindow 及 ShutdownMode 属性</title><link>https://blog.coldwind.top/posts/correct-way-to-use-mainwindow/</link><pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/correct-way-to-use-mainwindow/</guid><description>&lt;p>提起 &lt;code>MainWindow&lt;/code>，很多人的第一反应都是在创建 WPF 项目后，默认生成的那个 &lt;code>MainWindow&lt;/code> 类。但其实我们这次要讨论的，是 &lt;code>Application&lt;/code> 类中的 &lt;code>MainWindow&lt;/code> 属性，或者大家更熟悉的访问方式是 &lt;code>Application.Current.MainWindow&lt;/code>。可能有些人还不太清楚，但实际上它也是有一点使用技巧的，尤其是在与 &lt;code>ShutDownMode&lt;/code> 配合使用时。&lt;/p>
&lt;h2 id="单例模式">
单例模式
&lt;a href="#%e5%8d%95%e4%be%8b%e6%a8%a1%e5%bc%8f" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>首先，这个 &lt;code>MainWindow&lt;/code> 属性是一个单例模式的实现，所以我们可以在项目中的任何地方通过 &lt;code>Application.Current.MainWindow&lt;/code> 来访问它。这个功能非常方便，尤其是在需要在多个地方访问主窗口的情况下，比如在视图模型中或者其他服务类中。我们可以直接通过 &lt;code>Application.Current.MainWindow&lt;/code> 来获取主窗口的引用，而不需要想尽办法来获取或传递它的引用。这样的需求常见于需要在主界面展示消息、弹窗、导航等操作时。&lt;/p>
&lt;p>不过这里有一个稍微需要注意的地方，就是虽然它与我们的 &lt;code>MainWindow&lt;/code> 类同名，但它本身是一个 &lt;code>Window&lt;/code> 类型的属性，所以我们在使用时需要进行类型转换，这样才能拿到我们定义的 &lt;code>MainWindow&lt;/code> 类中的特定方法和属性，或者有名字的界面控件。&lt;/p>
&lt;h2 id="配合-shutdownmode-使用">
配合 ShutDownMode 使用
&lt;a href="#%e9%85%8d%e5%90%88-shutdownmode-%e4%bd%bf%e7%94%a8" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>这部分才是我们今天讨论的重点。WPF 开发者在刚入门的时候，可能会理所当然地认为，&lt;code>MainWindow&lt;/code> 就一定是 &lt;code>Application.Current.MainWindow&lt;/code> 的实例，并且主窗口关闭了，程序就应该退出了，这是天经地义的。但实际上这个事情恐怕没这么简单。&lt;/p>
&lt;h3 id="为什么-mainwindow-属性通常就是-mainwindow">
为什么 MainWindow 属性通常就是 MainWindow？
&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88-mainwindow-%e5%b1%9e%e6%80%a7%e9%80%9a%e5%b8%b8%e5%b0%b1%e6%98%af-mainwindow" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>一个简单的 WPF 程序中，我们通常可以在 &lt;code>App.xaml&lt;/code> 中看到类似这样的代码：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Application&lt;/span> &lt;span class="na">x:Class=&lt;/span>&lt;span class="s">&amp;#34;WpfApp.App&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">StartupUri=&lt;/span>&lt;span class="s">&amp;#34;MainWindow.xaml&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Application&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这段代码的意思是，当应用程序启动时，自动创建一个 &lt;code>MainWindow&lt;/code> 的实例，并将它设置为 &lt;code>Application.Current.MainWindow&lt;/code>。所以在这种情况下，&lt;code>MainWindow&lt;/code> 属性确实就是我们定义的 &lt;code>MainWindow&lt;/code> 类的实例。&lt;/p>
&lt;p>或者如果我们不希望 &lt;code>App&lt;/code> 自动帮我们创建主窗口，比如我们希望在程序刚开始的时候读取配置，为 DI 容器注册服务，或者做一些其他的初始化工作，我们可能会删掉 &lt;code>App.xaml&lt;/code> 中的 &lt;code>StartupUri&lt;/code>，并在 &lt;code>App.xaml.cs&lt;/code> 的 &lt;code>OnStartup&lt;/code> 方法中手动创建主窗口：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">protected&lt;/span> &lt;span class="kd">override&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">OnStartup&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">StartupEventArgs&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">base&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnStartup&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 这里我们手动创建 MainWindow 实例&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">mainWindow&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MainWindow&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">mainWindow&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Show&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在这种情况下，我们同样可以通过 &lt;code>Application.Current.MainWindow&lt;/code> 来访问这个主窗口，因为 WPF 默认会自动将第一个显示的窗口设置为 &lt;code>MainWindow&lt;/code> 属性的值。&lt;/p>
&lt;h3 id="shutdownmode-是什么">
ShutdownMode 是什么？
&lt;a href="#shutdownmode-%e6%98%af%e4%bb%80%e4%b9%88" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>&lt;code>ShutdownMode&lt;/code> 是 &lt;code>Application&lt;/code> 类中的一个属性，它决定了当应用程序应该退出的条件。它有三个选项：&lt;/p>
&lt;ul>
&lt;li>&lt;code>OnLastWindowClose&lt;/code>：当最后一个窗口关闭时，应用程序退出。这是默认值。&lt;/li>
&lt;li>&lt;code>OnMainWindowClose&lt;/code>：当主窗口关闭时，应用程序退出。&lt;/li>
&lt;li>&lt;code>OnExplicitShutdown&lt;/code>：只有当调用 &lt;code>Shutdown()&lt;/code> 方法时，应用程序才会退出。&lt;/li>
&lt;/ul>
&lt;p>虽然它的默认值是 &lt;code>OnLastWindowClose&lt;/code>，但实际上往往 &lt;code>OnMainWindowClose&lt;/code> 更加符合我们的预期，因为我们通常希望当主窗口关闭时，整个应用程序就退出了，而不需要担心其他窗口是否还在。因此主窗口本身还常常承载着很多善后工作，例如在 &lt;code>Closed&lt;/code> 事件中进行资源清理、保存数据和配置等操作。&lt;/p>
&lt;p>如果我们在主窗口的 &lt;code>Closed&lt;/code> 事件中再去额外调用 &lt;code>Application.Current.Shutdown()&lt;/code> 来保证其他窗口也会被关闭，这显然是不高明的。所以更好的做法是，直接将 &lt;code>ShutdownMode&lt;/code> 设置为 &lt;code>OnMainWindowClose&lt;/code>，这样当主窗口关闭时，整个应用程序就会自动退出了。我们只需要在 &lt;code>App.xaml&lt;/code> 或 &lt;code>App.xaml.cs&lt;/code> 中显式设置即可。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">&lt;p>关于 &lt;code>Application.Current.Shutdown()&lt;/code> 方法，还有一些值得提及的细节：&lt;/p>
&lt;ol>
&lt;li>通过调用它来关闭所有窗口，正常情况下是可以触发每个窗口的 &lt;code>Closing&lt;/code> 及 &lt;code>Closed&lt;/code> 事件的。&lt;/li>
&lt;li>如果窗口还订阅了 &lt;code>Closing&lt;/code> 事件，那么即便里面有 &lt;code>e.Cancel = true&lt;/code> 的代码逻辑，也不会阻止窗口的关闭。&lt;/li>
&lt;li>如果在 &lt;code>Closing&lt;/code> 事件中抛出了未经捕获的异常，可能会干扰事件链，导致 &lt;code>Closed&lt;/code> 事件不被触发。&lt;/li>
&lt;/ol>&lt;/div>
&lt;/div>
&lt;h3 id="第一个窗口不是-mainwindow-该怎么办">
第一个窗口不是 &lt;code>MainWindow&lt;/code> 该怎么办？
&lt;a href="#%e7%ac%ac%e4%b8%80%e4%b8%aa%e7%aa%97%e5%8f%a3%e4%b8%8d%e6%98%af-mainwindow-%e8%af%a5%e6%80%8e%e4%b9%88%e5%8a%9e" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>有时候，主窗口可能并不是第一个弹出的窗口。一个典型的例子是，我们在应用程序启动时，先弹出一个登录窗口，用户登录成功后才显示主窗口。在这种情况下，&lt;code>Application.Current.MainWindow&lt;/code> 可能会指向登录窗口，而不是我们真正的主窗口。那么此时我们该如何使用 &lt;code>OnMainWindowClose&lt;/code> 来确保主窗口关闭时应用程序退出呢？&lt;/p>
&lt;p>一个并不十分优雅，但也足够应付这个问题的方案是，我们先设置 &lt;code>ShutdownMode&lt;/code> 为 &lt;code>OnExplicitShutdown&lt;/code>，然后在登录窗口关闭后，再将 &lt;code>ShutdownMode&lt;/code> 设置为 &lt;code>OnMainWindowClose&lt;/code>，并且在登录窗口关闭时手动将主窗口设置为 &lt;code>Application.Current.MainWindow&lt;/code>。这样就能保证当主窗口关闭时，应用程序能够正确退出了。比如在 &lt;code>App.xaml.cs&lt;/code> 中：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">protected&lt;/span> &lt;span class="kd">override&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">OnStartup&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">StartupEventArgs&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">base&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnStartup&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 先设置 ShutdownMode 为 OnExplicitShutdown&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ShutdownMode&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">ShutdownMode&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnExplicitShutdown&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 显示登录窗口&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">loginWindow&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">LoginWindow&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">loginWindow&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ShowDialog&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 登录成功，创建主窗口&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">mainWindow&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MainWindow&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">mainWindow&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Show&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 将主窗口设置为 MainWindow 属性&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">MainWindow&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">mainWindow&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 设置 ShutdownMode 为 OnMainWindowClose&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ShutdownMode&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">ShutdownMode&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnMainWindowClose&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 登录失败，直接退出应用程序&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Shutdown&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这个方法不够优雅的地方在于，我们需要在登录窗口关闭后，手动设置 &lt;code>MainWindow&lt;/code> 属性，并且多次切换 &lt;code>ShutdownMode&lt;/code> 的值，从而让它们两个正确配合。&lt;/p>
&lt;h3 id="更优雅的方式">
更优雅的方式
&lt;a href="#%e6%9b%b4%e4%bc%98%e9%9b%85%e7%9a%84%e6%96%b9%e5%bc%8f" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>一个更优雅的方式为，我们可以先创建 &lt;code>MainWindow&lt;/code> 的实例，并且赋值给 &lt;code>Application.Current.MainWindow&lt;/code>，但不立即显示它。然后我们先显示登录窗口，等用户登录成功后，再显示主窗口。这样就能保证 &lt;code>MainWindow&lt;/code> 属性始终指向我们真正的主窗口了，同时也不需要频繁切换 &lt;code>ShutdownMode&lt;/code> 的值了。比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">protected&lt;/span> &lt;span class="kd">override&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">OnStartup&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">StartupEventArgs&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">base&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnStartup&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 先创建 MainWindow 实例，但不显示&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">MainWindow&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MainWindow&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ShutdownMode&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">ShutdownMode&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnMainWindowClose&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 显示登录窗口（省略判断登录成功的逻辑）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">loginWindow&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">LoginWindow&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">loginWindow&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ShowDialog&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 登录成功后显示主窗口&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">MainWindow&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Show&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但这时候肯定会有人会问：如果登录失败，那么 &lt;code>MainWindow&lt;/code> 不就白创建了？尤其是如果 &lt;code>MainWindow&lt;/code> 中包含巨量的初始化逻辑，会不会导致很大的开销，甚至让登录窗口都出现不流畅的情况？&lt;/p>
&lt;p>如果你有这样的担忧，那么很有可能是因为你的 &lt;code>MainWindow&lt;/code> 的实现方式并不合理，尤其是你将初始化逻辑直接放在了构造函数中。一般来说，我们应该将初始化逻辑放在 &lt;code>Loaded&lt;/code> 事件中，或者通过一些惰性加载的方式来实现，这样就能避免在创建 &lt;code>MainWindow&lt;/code> 实例时就进行大量的初始化工作了。&lt;/p>
&lt;p>或者，如果我们的项目采用了 MVVM 模式，那么我们可以将初始化逻辑放在 &lt;code>MainViewModel&lt;/code> 中，并在合适的时机（通常仍然是主窗口的 &lt;code>Loaded&lt;/code> 事件）调用它。这样同样可以避免上面的担忧。具体的做法可以是：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">partial&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MainWindow&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">Window&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">MainWindow&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">InitializeComponent&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Loaded&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">MainWindow_Loaded&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">MainWindow_Loaded&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="n">sender&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">RoutedEventArgs&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 在这里进行初始化逻辑&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">viewModel&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">MainViewModel&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">DataContext&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">viewModel&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Initialize&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>或者如果你不想在 &lt;code>.xaml.cs&lt;/code> 文件里面写太多逻辑，还可以用行为库：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="err">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:i=&lt;/span>&lt;span class="s">&amp;#34;http://schemas.microsoft.com/xaml/behaviors&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;i:Interaction.Triggers&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;i:EventTrigger&lt;/span> &lt;span class="na">EventName=&lt;/span>&lt;span class="s">&amp;#34;Loaded&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;i:InvokeCommandAction&lt;/span> &lt;span class="na">Command=&lt;/span>&lt;span class="s">&amp;#34;{Binding InitializeCommand}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/i:EventTrigger&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/i:Interaction.Triggers&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">除了将视图模型中的 &lt;code>Initialize&lt;/code> 方法包装为 &lt;code>ICommand&lt;/code> 来调用，还可以使用行为库中的 &lt;code>CallMethodAction&lt;/code> 来直接调用视图模型中的方法。不过不太推荐这样做，因为 &lt;code>ICommand&lt;/code>，尤其是 CommunityToolkit MVVM 中的 &lt;code>AsyncRelayCommand&lt;/code>，可以更好地处理异步操作、错误处理和命令状态管理等问题，而直接调用方法则需要我们自己来处理这些细节了。&lt;/div>
&lt;/div>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>总的来说，&lt;code>Application.Current.MainWindow&lt;/code> 是一个非常方便的属性，可以让我们在项目中的任何地方访问主窗口的实例。但我们需要注意它与 &lt;code>ShutDownMode&lt;/code> 的配合使用，尤其是在一些特殊的场景下，比如登录窗口和主窗口的关系。通过合理地设置 &lt;code>ShutdownMode&lt;/code> 和正确地初始化主窗口，我们就能确保当主窗口关闭时，整个应用程序能够正确退出了。&lt;/p></description></item><item><title>Jab：基于源生成器的编译时依赖注入容器</title><link>https://blog.coldwind.top/posts/introduce-di-tool-jab/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/introduce-di-tool-jab/</guid><description>&lt;p>最近在研究 Avalonia 的一款名为 &lt;a class="link" href="https://github.com/accntech/shad-ui" target="_blank" rel="noopener"
>ShadUI&lt;/a> 的主题库时，发现它的示例项目使用了 &lt;a class="link" href="https://github.com/pakrym/jab" target="_blank" rel="noopener"
>Jab&lt;/a> 这款 DI 容器来注册各种服务。&lt;/p>
&lt;p>源生成器诞生以来，越来越多曾经需要大量借助反射来实现的功能，都可以通过源生成器来实现，而无需再依赖反射。从 .NET 内置的 JSON 序列化和正则表达式，到 Mapperly 这款基于源生成器的映射库，他们每一个都跑分喜人。那么 Jab 会让我们失望吗？&lt;/p>
&lt;h2 id="基本用法">
基本用法
&lt;a href="#%e5%9f%ba%e6%9c%ac%e7%94%a8%e6%b3%95" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>Jab 的使用非常简单。安装它的 NuGet 包，或者在 .csproj 中添加以下内容即可：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ItemGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;PackageReference&lt;/span> &lt;span class="na">Include=&lt;/span>&lt;span class="s">&amp;#34;Jab&amp;#34;&lt;/span> &lt;span class="na">Version=&lt;/span>&lt;span class="s">&amp;#34;0.12.0&amp;#34;&lt;/span> &lt;span class="na">PrivateAssets=&lt;/span>&lt;span class="s">&amp;#34;all&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/ItemGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">这里的 &lt;code>PrivateAssets=&amp;quot;all&amp;quot;&lt;/code> 是为了防止 Jab 被打包到最终的 NuGet 包中。这个包本身的作用是为基于源生成器来生成 &lt;code>ServiceProvider&lt;/code> 以及注册各种服务的代码提供具体的实现。但是在生成后，我们直接使用这些生成出来的类即可，就不再需要 &lt;code>Jab&lt;/code> 本身了。&lt;/div>
&lt;/div>
&lt;p>然后就可以使用了。我们可以这样注册服务：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="na">[ServiceProvider]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[Transient(typeof(IService), typeof(ServiceImpl))]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">internal&lt;/span> &lt;span class="kd">partial&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MyServiceProvider&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>对于上面的代码，还有一点点“改进空间”：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="na">[ServiceProvider]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// C# 11 为我们带来了泛型特性&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[Transient&amp;lt;IService, ServiceImpl&amp;gt;]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 在有了主构造函数之后，我们声明一个空的类现在可以把花括号给省掉&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">internal&lt;/span> &lt;span class="kd">partial&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MyServiceProvider&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>注册好服务之后，直接实例化容器，然后调用 &lt;code>GetService&amp;lt;T&amp;gt;()&lt;/code> 获取服务即可：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">provider&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MyServiceProvider&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">IService&lt;/span> &lt;span class="n">service&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">provider&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetService&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">IService&lt;/span>&lt;span class="p">&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">Jab 没有类似 &lt;code>Microsoft.Extensions.DependencyInjection&lt;/code> 中 &lt;code>GetRequiredService&amp;lt;T&amp;gt;()&lt;/code> 的方法，但这并不是缺失——Jab 的 &lt;code>GetService&amp;lt;T&amp;gt;()&lt;/code> 返回的就是非空的 &lt;code>T&lt;/code> 而非可空的 &lt;code>T?&lt;/code>。如果你请求了一个未注册的服务，编译器会在编译期直接报错，而不是等到运行时才抛出异常。这正是编译时 DI 的核心优势之一。&lt;/div>
&lt;/div>
&lt;h2 id="特殊用法">
特殊用法
&lt;a href="#%e7%89%b9%e6%ae%8a%e7%94%a8%e6%b3%95" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>它的使用总体上来说是非常直观且符合直觉的。这里我再简单列举一些写法上稍微复杂一点的典型场景供大家快速参考。更多边界场景推荐直接阅读源代码仓库的 README。&lt;/p>
&lt;h2 id="将某个对象作为单例服务的实例">
将某个对象作为单例服务的实例
&lt;a href="#%e5%b0%86%e6%9f%90%e4%b8%aa%e5%af%b9%e8%b1%a1%e4%bd%9c%e4%b8%ba%e5%8d%95%e4%be%8b%e6%9c%8d%e5%8a%a1%e7%9a%84%e5%ae%9e%e4%be%8b" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>有时候，某个服务的实例需要通过特定的构建过程来创建，而不能简单地交给 DI 容器来实例化。典型的例子是 Serilog 的 &lt;code>ILogger&lt;/code>——它通常需要通过 &lt;code>LoggerConfiguration&lt;/code> 来配置和构建：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">ILogger&lt;/span> &lt;span class="n">logger&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">LoggerConfiguration&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">WriteTo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Console&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">WriteTo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">File&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;logs/app.log&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">rollingInterval&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">RollingInterval&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Day&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">CreateLogger&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>对于这种情况，Jab 提供了工厂方法的注册方式，让我们可以将这个已经构建好的实例作为单例注入：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="na">[ServiceProvider]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[Singleton&amp;lt;ILogger&amp;gt;(Factory = nameof(CreateLogger))]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">internal&lt;/span> &lt;span class="kd">partial&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MyServiceProvider&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="n">ILogger&lt;/span> &lt;span class="n">CreateLogger&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="n">LoggerConfiguration&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">WriteTo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Console&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">WriteTo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">File&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;logs/app.log&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">rollingInterval&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">RollingInterval&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Day&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">CreateLogger&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样，每次从容器中请求 &lt;code>ILogger&lt;/code> 时，都会返回同一个由工厂方法创建的实例。&lt;/p>
&lt;p>如果 logger 实例已经在容器外部创建好了，也可以直接通过实例属性的方式注册：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="na">[ServiceProvider]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[Singleton&amp;lt;ILogger&amp;gt;(Instance = nameof(LoggerInstance))]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">internal&lt;/span> &lt;span class="kd">partial&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MyServiceProvider&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">ILogger&lt;/span> &lt;span class="n">LoggerInstance&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>使用时，在容器创建后再将实例赋给该属性：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">ILogger&lt;/span> &lt;span class="n">logger&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">LoggerConfiguration&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">WriteTo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Console&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">CreateLogger&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">provider&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MyServiceProvider&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">provider&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">LoggerInstance&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">logger&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">ILogger&lt;/span> &lt;span class="n">resolved&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">provider&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetService&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">ILogger&lt;/span>&lt;span class="p">&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="命名服务">
命名服务
&lt;a href="#%e5%91%bd%e5%90%8d%e6%9c%8d%e5%8a%a1" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>当同一个接口有多个不同的实现需要同时注册时，可以通过 &lt;code>Name&lt;/code> 属性为每个注册取一个名字，然后在构造函数参数上用 &lt;code>[FromNamedServices(&amp;quot;...&amp;quot;)]&lt;/code> 来指定要注入哪一个：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="na">[ServiceProvider]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[Singleton&amp;lt;INotificationService, EmailNotificationService&amp;gt;(Name = &amp;#34;email&amp;#34;)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[Singleton&amp;lt;INotificationService, SmsNotificationService&amp;gt;(Name = &amp;#34;sms&amp;#34;)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[Singleton&amp;lt;Notifier&amp;gt;]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">internal&lt;/span> &lt;span class="kd">partial&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MyServiceProvider&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Notifier&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">Notifier&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [FromNamedServices(&amp;#34;email&amp;#34;)]&lt;/span> &lt;span class="n">INotificationService&lt;/span> &lt;span class="n">email&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [FromNamedServices(&amp;#34;sms&amp;#34;)]&lt;/span> &lt;span class="n">INotificationService&lt;/span> &lt;span class="n">sms&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">Jab 同样支持 &lt;code>Microsoft.Extensions.DependencyInjection&lt;/code> 中的 &lt;code>[FromKeyedServices]&lt;/code> 特性，两者可以互换使用。&lt;/div>
&lt;/div>
&lt;h2 id="模块">
模块
&lt;a href="#%e6%a8%a1%e5%9d%97" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>如果你用过 Autofac，对它的 &lt;code>Module&lt;/code> 机制一定不陌生。模块可以将一组相关的服务注册封装成一个独立单元，然后在不同的容器中复用，这样可以实现批量注册一些相关的服务的效果，从而更好地管理服务。Jab 也提供了类似的能力，同样叫做 Module。&lt;/p>
&lt;p>定义模块时，创建一个接口并标注 &lt;code>[ServiceProviderModule]&lt;/code>，然后在接口上声明服务注册：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="na">[ServiceProviderModule]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[Singleton&amp;lt;IService, ServiceImplementation&amp;gt;]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">interface&lt;/span> &lt;span class="nc">IMyModule&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在服务容器中用 &lt;code>[Import]&lt;/code> 引入模块即可：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="na">[ServiceProvider]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[Import&amp;lt;IMyModule&amp;gt;]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">internal&lt;/span> &lt;span class="kd">partial&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MyServiceProvider&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>模块之间也可以互相引入，便于将大型应用的注册拆分成多个职责清晰的模块来管理。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">&lt;code>Microsoft.Extensions.DependencyInjection&lt;/code> 本身没有 Module 的概念，通常的做法是为 &lt;code>IServiceCollection&lt;/code> 编写扩展方法（如 &lt;code>AddMyFeature(this IServiceCollection services)&lt;/code>）来实现类似的分组注册。Jab 将这一能力内置进来，写法上更加统一。&lt;/div>
&lt;/div>
&lt;h2 id="局限性">
局限性
&lt;a href="#%e5%b1%80%e9%99%90%e6%80%a7" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>Jab 虽然在性能和编译期安全性上表现亮眼，但在实际项目中引入之前，有几点局限性值得考量：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>生态兼容性&lt;/strong>：绝大多数第三方库（如 EF Core、Serilog、MediatR 等）都只针对 MEDI 提供 &lt;code>IServiceCollection&lt;/code> 扩展方法，无法直接用于 Jab，需要手动桥接或改用工厂方式注册。&lt;/li>
&lt;li>&lt;strong>没有 &lt;code>ServiceCollection&lt;/code>&lt;/strong>：Jab 没有运行时动态添加注册的能力，所有注册必须在编译期通过特性声明。这也导致所有针对 &lt;code>IServiceCollection&lt;/code> 的扩展方法及中间件（如 Scrutor 的扫描注册）均无法使用。&lt;/li>
&lt;li>&lt;strong>缺乏 Host 生态支持&lt;/strong>：&lt;code>Microsoft.Extensions.Hosting&lt;/code> 体系中的 &lt;code>IConfiguration&lt;/code>、&lt;code>IOptions&amp;lt;T&amp;gt;&lt;/code>、&lt;code>DbContext&lt;/code> 等基础设施与 Jab 并无原生集成，若项目依赖这些能力，引入成本会较高。&lt;/li>
&lt;li>&lt;strong>不适合动态插件系统&lt;/strong>：插件通常需要在运行时发现并注册服务，而 Jab 的注册完全发生在编译期，无法支持这类场景。&lt;/li>
&lt;li>&lt;strong>特性数量随项目增长&lt;/strong>：服务全部通过特性在容器类上声明，项目规模较大时，容器类顶部可能会堆积大量特性，可读性有所下降。不过借助 Module 可以在一定程度上缓解这个问题。&lt;/li>
&lt;/ol>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>Jab 是一个思路清晰的编译时 DI 容器。如果你的项目对启动性能敏感（如桌面应用、命令行工具、AOT 场景），或者希望在编译期就捕获依赖配置错误，Jab 是一个值得尝试的选择。&lt;/p>
&lt;p>但如果项目重度依赖 MEDI 生态（EF Core、ASP.NET Core 中间件、Generic Host 等），或者需要运行时动态注册，那么 Jab 并不适合作为主容器，继续使用 MEDI 会是更务实的选择。&lt;/p></description></item><item><title>在 C# 15 的 Union 类型到来之前我们都是怎么过的？</title><link>https://blog.coldwind.top/posts/what-we-do-before-union-type/</link><pubDate>Sat, 09 May 2026 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/what-we-do-before-union-type/</guid><description>&lt;p>截止到目前，.NET 11 已经出了三个预览版了，并且也为我们带来了万众期待的 Union 类型。关于这个类型，有不少大佬已经讨论过了，这里贴两个供大家参考：&lt;/p>
&lt;ul>
&lt;li>&lt;a class="link" href="https://zhuanlan.zhihu.com/p/2029276293867258416" target="_blank" rel="noopener"
>hez2010 的知乎文章&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://www.youtube.com/live/C5mozkE5x20" target="_blank" rel="noopener"
>Nick Chapsas 视频&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>所以这里我们就不赘述关于 Union 类型的语法、作用、底层原理之类的内容了。我们这次要聊一聊的是，在 Union 类型到来之前，我们都是怎么过的，或者说我们面临的问题都是如何解决的。通过对以往方式的讨论，我们或许会更加意识到 Union 类型有多么棒。&lt;/p>
&lt;h2 id="union-类型的核心目标">
Union 类型的核心目标
&lt;a href="#union-%e7%b1%bb%e5%9e%8b%e7%9a%84%e6%a0%b8%e5%bf%83%e7%9b%ae%e6%a0%87" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在回顾旧方案之前，我们先明确一下 Union 类型想要解决的核心问题：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>归类某些类型，但是不必共享行为&lt;/strong>：我们希望把几种相关的类型归为一组，但它们之间不需要有继承关系或共同的接口实现。&lt;/li>
&lt;li>&lt;strong>在编译时能够穷尽各种情况&lt;/strong>：当我们对一个联合类型的值进行处理时，编译器能够检查我们是否覆盖了所有可能的情况。&lt;/li>
&lt;li>&lt;strong>减少心智负担和运行时错误&lt;/strong>：类型系统应该在编译期就帮我们排除掉&amp;quot;忘记处理某种情况&amp;quot;的隐患。&lt;/li>
&lt;li>&lt;strong>锦上添花，最好还能减少装箱的开销&lt;/strong>。&lt;/li>
&lt;/ol>
&lt;p>带着这些目标，我们来看看过去 C# 开发者们是怎么做的。&lt;/p>
&lt;h2 id="枚举类型-enum">
枚举类型 enum
&lt;a href="#%e6%9e%9a%e4%b8%be%e7%b1%bb%e5%9e%8b-enum" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在了解 Union 类型时，我们常常会看到诸如返回不同状态的代码，比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="n">union&lt;/span> &lt;span class="n">Result&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Error&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="n">Result&lt;/span> &lt;span class="n">GetResult&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="cm">/* 最终返回 Result 中的某种具体类型的实例 */&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在没有 Union 类型的时候，很多人第一反应就是使用枚举：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">enum&lt;/span> &lt;span class="n">Status&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Success&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Failed&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="n">Status&lt;/span> &lt;span class="n">GetResult&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="cm">/* ... */&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>枚举类型本质上是值类型，只能包含单个分类，&lt;strong>不能包含更多的内部信息&lt;/strong>。如果 &lt;code>Success&lt;/code> 需要携带返回数据，&lt;code>Failed&lt;/code> 需要携带错误信息，枚举就无能为力了。你不得不额外定义配套的字段或类来传递这些信息，导致数据和状态分离，增加了维护成本。&lt;/p>
&lt;h2 id="包含多个属性的类">
包含多个属性的类
&lt;a href="#%e5%8c%85%e5%90%ab%e5%a4%9a%e4%b8%aa%e5%b1%9e%e6%80%a7%e7%9a%84%e7%b1%bb" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>既然枚举无法携带数据，很自然的想法就是定义一个&amp;quot;大而全&amp;quot;的类，把所有可能用到的属性都放进去：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Result&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">T&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">Data&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">Exception&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">Error&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">IsSuccess&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Error&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样的类型无法限制类成员之间的关系，只能依靠人为的约束和尽可能多的防御性编程。&lt;/p>
&lt;p>比如上面这个例子，可能会出现 &lt;code>Data&lt;/code> 和 &lt;code>Error&lt;/code> 同时存在的情况，导致 &lt;code>IsSuccess&lt;/code> 不可靠。如果成员变多，会更加不可控，势必会出现更多人为的检查：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">GetResult&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IsSuccess&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 编译器无法保证 result.Data 不为 null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 我们只能祈祷调用方遵守约定&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Data&lt;/span>&lt;span class="p">!.&lt;/span>&lt;span class="n">ToString&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>类型系统在这里完全帮不上忙，正确性全靠程序员的自觉和代码审查。&lt;/p>
&lt;h2 id="object-类型">
object 类型
&lt;a href="#object-%e7%b1%bb%e5%9e%8b" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>将变量声明为 &lt;code>object&lt;/code> 类型确实可以一定程度上实现表示多种类型的能力，并且借助模式匹配及 &lt;code>switch&lt;/code> 语法可以实现灵活的功能：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">object&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">GetSomething&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">switch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">$&amp;#34;Integer: {i}&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">$&amp;#34;String: {s}&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">default&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">NotSupportedException&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但有几个明显的问题：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>编译时无法得知变量类型，缺乏代码提示&lt;/strong>：&lt;code>object&lt;/code> 可以表示任何东西，IDE 无法给出有意义的智能提示。&lt;/li>
&lt;li>&lt;strong>难以涵盖所有情况&lt;/strong>：编译器不会检查你是否处理了所有可能的类型，必须手动提供兜底方案（&lt;code>default&lt;/code> 分支）。&lt;/li>
&lt;li>&lt;strong>可能存在装箱拆箱的开销&lt;/strong>：值类型赋值给 &lt;code>object&lt;/code> 时会发生装箱，带来额外的性能开销。&lt;/li>
&lt;li>&lt;strong>类型安全性差&lt;/strong>：任何类型都可以赋值进去，运行时出错的风险很高。&lt;/li>
&lt;/ol>
&lt;h2 id="空的抽象基类或接口">
空的抽象基类或接口
&lt;a href="#%e7%a9%ba%e7%9a%84%e6%8a%bd%e8%b1%a1%e5%9f%ba%e7%b1%bb%e6%88%96%e6%8e%a5%e5%8f%a3" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>比 &lt;code>object&lt;/code> 类型稍微好一点的做法是定义一个空的抽象基类或接口：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">abstract&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Result&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Success&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">Result&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">T&lt;/span> &lt;span class="n">Data&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">Success&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">T&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Data&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Error&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">Result&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Message&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Message&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样可以一定程度上限制可能性，提高编译时期的严谨性，但仍然有几个问题：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>必须保证所有类型都继承自该抽象类&lt;/strong>：对于内置类型（如 &lt;code>int&lt;/code>、&lt;code>string&lt;/code>）或者来自第三方库的类型，我们完全无能为力，无法把它们纳入这个联合体系。&lt;/li>
&lt;li>&lt;strong>仍然需要提供兜底方案&lt;/strong>：&lt;code>switch&lt;/code> 表达式中编译器不知道有哪些派生类，所以 exhaustive check 无从谈起。&lt;/li>
&lt;li>&lt;strong>无法保证封闭性&lt;/strong>：其他开发者随时可以写出新的继承该抽象类的类型，而你无法阻止。这就破坏了&amp;quot;有限种可能&amp;quot;的语义。&lt;/li>
&lt;/ol>
&lt;h2 id="oneof-第三方库">
OneOf 第三方库
&lt;a href="#oneof-%e7%ac%ac%e4%b8%89%e6%96%b9%e5%ba%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在 C# 社区中，&lt;a class="link" href="https://github.com/mcintyre321/OneOf" target="_blank" rel="noopener"
>OneOf&lt;/a> 是一个非常流行的用于模拟 Union 类型的库。它的基本用法如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">OneOf&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="n">OneOf&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">User&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">NotFound&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ValidationError&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">GetUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span> &lt;span class="p">&amp;lt;=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Invalid ID&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">user&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">_db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FindUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">user&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">NotFound&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">user&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 使用&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">GetUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">42&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Match&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">user&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Name&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">notFound&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;User not found&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">error&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Message&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这个库确实可以实现类型的联合，以及编译时的穷尽检查（通过 &lt;code>Match&lt;/code> 方法要求你处理所有类型）。但存在一些弊端：&lt;/p>
&lt;h3 id="缺乏语言级别的语法糖">
缺乏语言级别的语法糖
&lt;a href="#%e7%bc%ba%e4%b9%8f%e8%af%ad%e8%a8%80%e7%ba%a7%e5%88%ab%e7%9a%84%e8%af%ad%e6%b3%95%e7%b3%96" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>它不是语言特性，无法享受 C# 语法糖带来的便捷。你只能用它自己的 &lt;code>Match&lt;/code> 等方法，&lt;strong>不能用原生的 &lt;code>switch&lt;/code> 语句、模式匹配等&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 这是做不到的：&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">GetUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">42&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">message&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="k">switch&lt;/span> &lt;span class="c1">// ❌ 编译错误&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">User&lt;/span> &lt;span class="n">u&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">u&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Name&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">NotFound&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="s">&amp;#34;Not found&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ValidationError&lt;/span> &lt;span class="n">e&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Message&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="代码繁杂">
代码繁杂
&lt;a href="#%e4%bb%a3%e7%a0%81%e7%b9%81%e6%9d%82" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>当联合的类型较多时，代码会显得繁杂。类型声明变成 &lt;code>OneOf&amp;lt;T1, T2, ..., Tn&amp;gt;&lt;/code>，而 &lt;code>Match&lt;/code> 语法中需要大量的 lambda 表达式：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">OneOf&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">double&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">bool&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">DateTime&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="k">value&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">...;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">value&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Match&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">i&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">HandleInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">s&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">HandleString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">d&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">HandleDouble&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">d&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">b&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">HandleBool&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dt&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">HandleDateTime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dt&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="缺乏领域签名">
缺乏领域签名
&lt;a href="#%e7%bc%ba%e4%b9%8f%e9%a2%86%e5%9f%9f%e7%ad%be%e5%90%8d" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>&lt;code>OneOf&amp;lt;User, NotFound, ValidationError&amp;gt;&lt;/code> 这样的类型虽然能表达联合，但缺乏语义上的清晰度。每次看到这个类型，你都需要在脑海中翻译一遍它的含义。&lt;/p>
&lt;p>相比之下，&lt;code>union Result(User, NotFound, ValidationError)&lt;/code> 这样的语法更直观，能够直接从类型定义中理解它的用途和意义。&lt;/p>
&lt;p>&lt;code>OneOf&amp;lt;int, string&amp;gt;&lt;/code> 则更是难以理解它的用途——它到底表示什么业务含义？无法从类型定义中推断出它的意义。&lt;/p>
&lt;h3 id="泛型参数顺序泄露到-api-设计里">
泛型参数顺序泄露到 API 设计里
&lt;a href="#%e6%b3%9b%e5%9e%8b%e5%8f%82%e6%95%b0%e9%a1%ba%e5%ba%8f%e6%b3%84%e9%9c%b2%e5%88%b0-api-%e8%ae%be%e8%ae%a1%e9%87%8c" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>&lt;code>OneOf&amp;lt;User, NotFound&amp;gt;&lt;/code> 和 &lt;code>OneOf&amp;lt;NotFound, User&amp;gt;&lt;/code> 是两个&lt;strong>不同的类型&lt;/strong>，虽然它们表达的语义是一样的。这意味着如果你在一个地方用了 &lt;code>OneOf&amp;lt;A, B&amp;gt;&lt;/code>，另一个地方用了 &lt;code>OneOf&amp;lt;B, A&amp;gt;&lt;/code>，它们之间无法直接兼容，这会给 API 设计带来不必要的约束和混乱。&lt;/p>
&lt;h2 id="union-类型的到来">
Union 类型的到来
&lt;a href="#union-%e7%b1%bb%e5%9e%8b%e7%9a%84%e5%88%b0%e6%9d%a5" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>现在，让我们看看 C# 15 的 Union 类型如何解决上述所有问题：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">record&lt;/span> &lt;span class="nc">class&lt;/span> &lt;span class="n">User&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">Name&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">record&lt;/span> &lt;span class="nc">class&lt;/span> &lt;span class="n">NotFound&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">record&lt;/span> &lt;span class="nc">class&lt;/span> &lt;span class="n">ValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">Message&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="n">union&lt;/span> &lt;span class="n">Result&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">User&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">NotFound&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ValidationError&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="n">Result&lt;/span> &lt;span class="n">GetUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span> &lt;span class="p">&amp;lt;=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Invalid ID&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">user&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">_db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FindUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">user&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">NotFound&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">user&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 隐式转换&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 使用&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">GetUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">42&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ✅ 原生支持 switch 表达式和模式匹配&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">message&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="k">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">User&lt;/span> &lt;span class="n">u&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="s">$&amp;#34;Found: {u.Name}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">NotFound&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="s">&amp;#34;User not found&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ValidationError&lt;/span> &lt;span class="n">e&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="s">$&amp;#34;Error: {e.Message}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 不需要兜底分支！编译器知道只有这三种可能&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="底层原理">
底层原理
&lt;a href="#%e5%ba%95%e5%b1%82%e5%8e%9f%e7%90%86" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>本质上，Union 类型是编译器帮你生成的一个结构体，加上一个 &lt;code>object?&lt;/code> 字段和隐式转换。当声明 &lt;code>union Pet(Cat, Dog, Bird)&lt;/code> 时，实际上编译器后台会生成类似下面的代码：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="na">[System.Runtime.CompilerServices.Union]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="nc">Pet&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Runtime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CompilerServices&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IUnion&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 为每个情况类型生成构造函数&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">Pet&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Cat&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Value&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">Pet&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Dog&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Value&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">Pet&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Bird&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Value&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 核心存储：单个 object? 字段&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">object?&lt;/span> &lt;span class="n">Value&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>核心组件是 &lt;code>IUnion&lt;/code> 接口，以及 &lt;code>[Union]&lt;/code> 特性：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">interface&lt;/span> &lt;span class="nc">IUnion&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">object?&lt;/span> &lt;span class="n">Value&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>编译器会借助隐式转换等方式，在后台实现相关逻辑：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">Pet&lt;/span> &lt;span class="n">pet&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Dog&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Rex&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 实际上是 new Pet(new Dog(&amp;#34;Rex&amp;#34;))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">pet&lt;/span> &lt;span class="k">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Dog&lt;/span> &lt;span class="n">d&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">d&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 编译器检查：pet.Value is Dog d&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Cat&lt;/span> &lt;span class="n">c&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Name&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Bird&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Name&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">如果你现在就想体验 Union 类型，可以下载 .NET 11 预览版 SDK。不过需要注意的是，早期预览版中 &lt;code>UnionAttribute&lt;/code> 和 &lt;code>IUnion&lt;/code> 尚未内置在运行时中，需要手动添加 Polyfill 代码。&lt;/div>
&lt;/div>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>回顾过去，C# 开发者为了模拟 Union 类型的能力，尝试过枚举、&amp;ldquo;大而全&amp;quot;的类、&lt;code>object&lt;/code>、抽象基类、第三方库等各种方案。但它们各自都有明显的缺陷：要么无法携带数据，要么缺乏类型安全，要么语法繁琐，要么语义不清晰。&lt;/p>
&lt;p>C# 15 引入的原生 Union 类型，不仅提供了简洁优雅的语法，更重要的是它让类型系统能够在编译期就帮我们保证正确性——穷尽检查、隐式转换、原生模式匹配支持，这些都是过去任何方案都无法同时满足的。&lt;/p>
&lt;p>Union 类型的到来，标志着 C# 在类型系统上又迈出了一大步。对于那些熟悉 F#、Rust 或 TypeScript 中联合类型的开发者来说，绝对算得上等来了自己的“福报”。期待 C# 在未来能继续引入更多强大的类型特性，让我们的代码更安全、更简洁、更易维护。&lt;/p></description></item><item><title>不要轻易使用大而全的扩展库</title><link>https://blog.coldwind.top/posts/dont-easily-use-comprehensive-extension-libraries/</link><pubDate>Thu, 05 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/dont-easily-use-comprehensive-extension-libraries/</guid><description>&lt;p>NuGet 上有很多大而全的 C# 扩展库。它们通常会为各种常用的类型提供相当多的扩展方法，从而辅助我们的开发。最常见的功能有：&lt;/p>
&lt;ul>
&lt;li>字符串处理&lt;/li>
&lt;li>集合处理&lt;/li>
&lt;li>文件操作&lt;/li>
&lt;li>序列化与反序列化&lt;/li>
&lt;li>网络请求&lt;/li>
&lt;li>日期时间处理&lt;/li>
&lt;/ul>
&lt;p>等等。但我的建议是，&lt;strong>不要轻易使用这些大而全的扩展库&lt;/strong>。我们今天就来探讨一下这个问题。为了避免高级黑或者拉踩之类的嫌疑，这次我不会点名具体的库，但是会通过一些例子教大家如何判断一个库是否适合使用，以及该如何正确地学习和利用它们。&lt;/p>
&lt;h2 id="原因一功能大多数用不上而且污染基本类型">
原因一：功能大多数用不上，而且污染基本类型
&lt;a href="#%e5%8e%9f%e5%9b%a0%e4%b8%80%e5%8a%9f%e8%83%bd%e5%a4%a7%e5%a4%9a%e6%95%b0%e7%94%a8%e4%b8%8d%e4%b8%8a%e8%80%8c%e4%b8%94%e6%b1%a1%e6%9f%93%e5%9f%ba%e6%9c%ac%e7%b1%bb%e5%9e%8b" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>首先最重要的一个原因就是，这些扩展库往往会提供上百个扩展方法，但是我们实际用得到的通常只有 10 个以内。而为了用这几个功能，我们却不得不引入整个库，从而导致我们的项目变得臃肿。&lt;/p>
&lt;p>更糟糕的是，这些扩展库因为充分扩展了常见的数据类型（比如 &lt;code>string&lt;/code>、&lt;code>IEnumerable&amp;lt;T&amp;gt;&lt;/code>、&lt;code>DateTime&lt;/code> 等等），会导致代码补全时出现大量无关的扩展方法，影响开发效率。如果我们将这个库引入到团队项目中，那就更麻烦了，团队成员在编写代码时也会被这些无关的扩展方法干扰。&lt;/p>
&lt;p>可能有人会说，这些扩展库都会将方法放在特定的命名空间下，我们只需要不引入这个命名空间就行了。理论上是这样没错，但实际上现在的 IDE 都相当智能。即便你没有引入命名空间，通常也会看到这些方法的提示，并且如果你不慎使用，IDE 还会自动帮你添加 &lt;code>using&lt;/code> 语句，从而引入了整个扩展库。所以只要你的项目添加了这个扩展库，就很难避免污染。&lt;/p>
&lt;h2 id="原因二扩展库可能引入不必要的依赖">
原因二：扩展库可能引入不必要的依赖
&lt;a href="#%e5%8e%9f%e5%9b%a0%e4%ba%8c%e6%89%a9%e5%b1%95%e5%ba%93%e5%8f%af%e8%83%bd%e5%bc%95%e5%85%a5%e4%b8%8d%e5%bf%85%e8%a6%81%e7%9a%84%e4%be%9d%e8%b5%96" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>有些扩展库为了充分向前兼容，往往会使用一些比较老旧的实现方式，甚至现在已经被标记为过时的 .NET 内置方法。比如为了实现序列化反序列化，它可能会引入 &lt;code>Newtonsoft.Json&lt;/code>，而不是使用现在更推荐的 &lt;code>System.Text.Json&lt;/code>，甚至有的可能还在使用 &lt;code>System.Runtime.Serialization.Json&lt;/code> 命名空间下的老旧方法；又比如网络请求，它可能会使用 &lt;code>HttpWebRequest&lt;/code>，而不是现在更推荐的 &lt;code>HttpClient&lt;/code>。&lt;/p>
&lt;p>这些都会导致我们的项目引入一些不必要的依赖，从而增加项目的复杂度和体积。&lt;/p>
&lt;h2 id="原因三使用过时的标准库方法">
原因三：使用过时的标准库方法
&lt;a href="#%e5%8e%9f%e5%9b%a0%e4%b8%89%e4%bd%bf%e7%94%a8%e8%bf%87%e6%97%b6%e7%9a%84%e6%a0%87%e5%87%86%e5%ba%93%e6%96%b9%e6%b3%95" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>这些扩展库可能因为要兼容更早版本的 .NET，或因为过于庞杂难以更新，往往会使用一些已经过时的标准库方法，从而影响我们的代码质量和性能。&lt;/p>
&lt;h3 id="案例一哈希">
案例一：哈希
&lt;a href="#%e6%a1%88%e4%be%8b%e4%b8%80%e5%93%88%e5%b8%8c" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>我见过一些情形，比如计算 MD5，它虽然使用了 .NET 内置的 &lt;code>MD5&lt;/code> 类，但却没有使用现在更推荐的方式：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 过时的用法&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">md5&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">MD5&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Create&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">hash&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">md5&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ComputeHash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 现代的用法&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">hash&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">MD5&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">HashData&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这看起来可能没什么，但实际上这两种方式的性能差别还是很可观的。传统的 &lt;code>MD5.Create()&lt;/code> 会在托管堆上创建一个加密算法实例，而 .NET 5 新增的 &lt;code>MD5.HashData&lt;/code> 是静态方法，内部通常会复用或通过更底层（往往是无状态的）的方式直接调用操作系统或运行时的加密库。&lt;/p>
&lt;p>不仅如此，&lt;code>HashData&lt;/code> 提供支持 &lt;code>ReadOnlySpan&amp;lt;byte&amp;gt;&lt;/code> 的重载。必要的情况下，我们可以将哈希值直接写入预先分配好的缓冲区（如 stackalloc 分配的栈内存），从而实现完全不分配堆内存。&lt;/p>
&lt;p>简单跑一个分，可以看到新方法不仅效率高，而且几乎没有 GC 开销：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Method&lt;/th>
&lt;th style="text-align: right">Mean&lt;/th>
&lt;th style="text-align: right">Error&lt;/th>
&lt;th style="text-align: right">StdDev&lt;/th>
&lt;th style="text-align: right">Gen0&lt;/th>
&lt;th style="text-align: right">Gen1&lt;/th>
&lt;th style="text-align: right">Allocated&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>ComputeHash&lt;/td>
&lt;td style="text-align: right">335.7 ns&lt;/td>
&lt;td style="text-align: right">35.04 ns&lt;/td>
&lt;td style="text-align: right">1.92 ns&lt;/td>
&lt;td style="text-align: right">0.0172&lt;/td>
&lt;td style="text-align: right">0.0005&lt;/td>
&lt;td style="text-align: right">216 B&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>HashData&lt;/td>
&lt;td style="text-align: right">192.1 ns&lt;/td>
&lt;td style="text-align: right">11.67 ns&lt;/td>
&lt;td style="text-align: right">0.64 ns&lt;/td>
&lt;td style="text-align: right">0.0031&lt;/td>
&lt;td style="text-align: right">0.0002&lt;/td>
&lt;td style="text-align: right">40 B&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>因此，如果我们使用这些大而全的扩展库，可能会无意中引入一些过时的实现方式，影响我们的代码质量和性能。&lt;/p>
&lt;h3 id="案例二字符串处理">
案例二：字符串处理
&lt;a href="#%e6%a1%88%e4%be%8b%e4%ba%8c%e5%ad%97%e7%ac%a6%e4%b8%b2%e5%a4%84%e7%90%86" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>字符串处理也是一个重灾区。因为在最近几年，&lt;code>Span&lt;/code> 和 &lt;code>Memory&lt;/code> 的引入，.NET 在字符串处理方面有了很大的改进。很多以前需要分配堆内存的操作，现在都可以通过 &lt;code>Span&amp;lt;char&amp;gt;&lt;/code> 来实现零分配的高效处理。除此之外，现在的 .NET 底层还会利用 SIMD 指令集来加速字符串处理操作。&lt;/p>
&lt;p>比如我见过有一个库提供了 &lt;code>ContainsAll&lt;/code> 方法，用于检查一个字符串是否包含多个子串。这个方法的实现是这样的：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">ContainsAll&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">source&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">IEnumerable&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">values&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="k">value&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">values&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">source&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IndexOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这是一个在现在看来比较糟糕的实现。对于判断是否包含子字符串这样的需求，用 &lt;code>Contains&lt;/code> 的效率是显著高于 &lt;code>IndexOf&lt;/code> 的，因为 &lt;code>Contains&lt;/code> 方法在底层已经做了很多优化，比如借助 &lt;code>Span&lt;/code>，以及利用 SIMD 指令集来加速搜索过程。这里就不贴跑分了，我在自己的设备上测出了超过 40 倍的性能提升。&lt;/p>
&lt;p>所以一个更加高效的实现方式可以是：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">ContainsAll&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">source&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">IEnumerable&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">values&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">values&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">All&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">source&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>是的，LINQ 在最近几个版本的 .NET 中也迎来了相当多的更新。许多常用的方法现在都可以做到没有 GC 开销。但遗憾的是，这些新变化在那些扩展库诞生的年代可能还没有出现，所以它们的实现方式往往比较落后。&lt;/p>
&lt;h2 id="我的建议">
我的建议
&lt;a href="#%e6%88%91%e7%9a%84%e5%bb%ba%e8%ae%ae" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>虽然我上面提到了这些问题，而且我确实不建议（至少是在现在）使用这些大而全的扩展库，但我并不是说它们完全没有价值。我们可以用下面几种方式来充分利用或学习它们：&lt;/p>
&lt;ol>
&lt;li>我们要面对的是很老的项目（例如 .NET Framework 4.x）。这种情况下，.NET 的后续很多优化和新的标准库方法都还没有出现，所以就更谈不上性能问题了。我们可以直接使用这些扩展库来提升开发效率，和弥补旧标准库缺失的一些功能。&lt;/li>
&lt;li>我们可以学习和借鉴这些扩展库的设计思路和实现方式。虽然它们可能在性能上不够理想，但在功能设计和 API 设计上，还是有很多值得我们学习的地方的。&lt;/li>
&lt;li>我们可以从这些扩展库中挑选出我们真正需要的功能，然后自己实现一个轻量级的版本，或者只是简单地复制粘贴到我们自己的项目中。这样既能满足我们的需求，又能避免引入不必要的依赖和性能问题。&lt;/li>
&lt;/ol>
&lt;p>总之，&lt;strong>不要轻易使用大而全的扩展库&lt;/strong>，我们应该根据自己的实际需求来选择和使用这些库，同时也要注意它们可能带来的性能和依赖问题。&lt;/p></description></item><item><title>如何在本地客户端使用 EF Core</title><link>https://blog.coldwind.top/posts/use-efcore-in-client-app/</link><pubDate>Mon, 02 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/use-efcore-in-client-app/</guid><description>&lt;p>Entity Framework Core (EF Core) 是一个强大的对象关系映射（ORM）框架，通常用于服务器端应用程序中与数据库交互。通常我们会将它用于 ASP.NET Core 应用程序，并且它的配置方式也绝对可以说是相当成熟了。但有时候我们在做本地客户端应用程序（例如 WPF、WinForms 或 Avalonia 应用程序）时，也希望利用 EF Core 来简化数据访问层的开发。那么我们需要注意些什么？最佳实践是怎样的？这篇文章我们就来探讨这个问题。&lt;/p>
&lt;h2 id="回顾-ef-core-在-aspnet-core-中的使用">
回顾 EF Core 在 ASP.NET Core 中的使用
&lt;a href="#%e5%9b%9e%e9%a1%be-ef-core-%e5%9c%a8-aspnet-core-%e4%b8%ad%e7%9a%84%e4%bd%bf%e7%94%a8" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>首先我们来快速回顾一下在 ASP.NET Core 应用程序中使用 EF Core 的典型方式。以一个比较新（.NET 6+）的项目为例，通常我们会在程序入口进行如下的配置：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">builder&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">WebApplication&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CreateBuilder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 配置 DbContext&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">builder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Services&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AddDbContext&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">AppDbContext&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="n">options&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">options&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">UseSqlServer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">builder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Configuration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetConnectionString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;DefaultConnection&amp;#34;&lt;/span>&lt;span class="p">)));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 其他服务配置&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里其实有一个隐式的配置，就是我们将 &lt;code>DbContext&lt;/code> 的生命周期设置为了作用域（Scoped）。在 ASP.NET Core 中，每个 HTTP 请求都会创建一个新的作用域，因此每个请求都会有一个独立的 &lt;code>DbContext&lt;/code> 实例。这种方式有助于确保数据的一致性和隔离性。&lt;/p>
&lt;p>这个方案在服务器端应用程序中可以说是非常标准且正确的，但是这个问题到了本地客户端，情况就不太相同了。对于客户端程序来说，通常并没有作用域这么一个概念。客户端程序一般是单用户的，整个应用程序的生命周期就是一个大的作用域。如果我们直接将 &lt;code>DbContext&lt;/code> 注册为作用域生命周期，那么实际上它就会变成单例生命周期，这样就会带来一些问题：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>线程安全问题&lt;/strong>：&lt;code>DbContext&lt;/code> 不是线程安全的，如果在多线程环境下（例如 UI 线程和后台线程）共享同一个实例，可能会导致数据损坏或异常。&lt;/li>
&lt;li>&lt;strong>内存泄漏&lt;/strong>：长时间持有 &lt;code>DbContext&lt;/code> 实例可能会导致内存泄漏，因为它会跟踪所有的实体状态，随着时间的推移，这些状态会不断累积。&lt;/li>
&lt;/ol>
&lt;p>因此，如何解决这两个问题就成了我们在客户端使用 EF Core 时需要重点考虑的内容。&lt;/p>
&lt;h2 id="方法一注册为瞬态transient生命周期">
方法一：注册为瞬态（Transient）生命周期
&lt;a href="#%e6%96%b9%e6%b3%95%e4%b8%80%e6%b3%a8%e5%86%8c%e4%b8%ba%e7%9e%ac%e6%80%81transient%e7%94%9f%e5%91%bd%e5%91%a8%e6%9c%9f" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>首先我们来看一种相对来说比较简单的方式。既然注册为作用域，实际上就相当于单例，那么我们可以直接将 &lt;code>DbContext&lt;/code> 注册为瞬态生命周期。这样在每个被注入服务的类中，它都会获得一个新的 &lt;code>DbContext&lt;/code> 实例，从而一定程度上避免了一些问题。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 注册 DbContext 服务&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">builder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Services&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AddDbContext&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">AppDbContext&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="n">options&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">options&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">UseSqlServer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">builder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Configuration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetConnectionString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;DefaultConnection&amp;#34;&lt;/span>&lt;span class="p">)),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ServiceLifetime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Transient&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 使用 DbContext 的服务&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MyViewModel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">AppDbContext&lt;/span> &lt;span class="n">_dbContext&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">MyViewModel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">AppDbContext&lt;/span> &lt;span class="n">dbContext&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_dbContext&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">dbContext&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">LoadData&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">items&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">_dbContext&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToList&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 处理数据&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">对于一个客户端程序，尤其是 MVVM 模式，一个典型的情况是我们将 &lt;code>DbContext&lt;/code> 注入到 ViewModel 中使用。当然对于更复杂的场景，我们可能会将数据访问逻辑封装到一个 Repository 或 Service 层中，然后再将这些服务注入到 ViewModel 中。至于没有使用 MVVM 模式的情形，我们基本上也不会搞 DI 容器，所以也就不考虑这些问题了。&lt;/div>
&lt;/div>
&lt;p>那么这有没有问题呢？答案是有的，而且这个方案并不理想。比如我们看下面这个例子。在这个例子中，我们使用了 CommunityToolkit.Mvvm 来简化 ViewModel 的编写：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">partial&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MainViewModel&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">ViewModelBase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">AppDbContext&lt;/span> &lt;span class="n">_dbContext&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [ObservableProperty]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="n">ObservableCollection&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Item&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_loadedItems&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">MainViewModel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">AppDbContext&lt;/span> &lt;span class="n">dbContext&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_dbContext&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">dbContext&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [RelayCommand(AllowConcurrent = true)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kd">async&lt;/span> &lt;span class="n">Task&lt;/span> &lt;span class="n">LoadDataAsync&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">items&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">_dbContext&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToListAsync&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">LoadedItems&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">items&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在这个例子中，我们将 &lt;code>DbContext&lt;/code> 注入到了 &lt;code>MainViewModel&lt;/code> 中，并且在 &lt;code>LoadDataAsync&lt;/code> 方法中使用它来加载数据。这里其实就已经出现不少问题了：&lt;/p>
&lt;ol>
&lt;li>&lt;code>MainViewModel&lt;/code> 的实例通常生命周期非常长（可能与整个应用程序相同），而 &lt;code>DbContext&lt;/code> 被注册为瞬态生命周期，这就意味着每次调用 &lt;code>LoadDataAsync&lt;/code> 方法时，实际上都是在使用同一个 &lt;code>DbContext&lt;/code> 实例。那这其实并没有比单例强多少，最多就是不用和其他服务共用同一个实例而已。长时间使用下去，&lt;code>DbContext&lt;/code> 仍然会积累大量的状态，导致内存泄漏的问题。&lt;/li>
&lt;li>这里我故意将 &lt;code>LoadDataAsync&lt;/code> 方法设置为允许并发执行（&lt;code>AllowConcurrent = true&lt;/code>）。这就意味着如果用户快速多次点击加载按钮，就会导致多个线程同时访问同一个 &lt;code>DbContext&lt;/code> 实例，从而引发线程安全问题。&lt;/li>
&lt;/ol>
&lt;p>所以说，虽然将 &lt;code>DbContext&lt;/code> 注册为瞬态生命周期在某些情况下可以工作，但它并不是一个理想的解决方案。除非我们能保证使用它的代码绝对不会并发执行，并且生命周期比较短（比如一个表单的视图模型），否则我们还是需要寻找更好的方法。&lt;/p>
&lt;h2 id="方法二为服务注入作用域">
方法二：为服务注入作用域
&lt;a href="#%e6%96%b9%e6%b3%95%e4%ba%8c%e4%b8%ba%e6%9c%8d%e5%8a%a1%e6%b3%a8%e5%85%a5%e4%bd%9c%e7%94%a8%e5%9f%9f" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>既然上面的思路走不通，我们恐怕就不能简单地给视图模型直接注入 &lt;code>DbContext&lt;/code> 了。为了解决上面的问题，我们应该让视图模型操作数据库的方法有机会每次都创建一个新的实例。顺着这个思路，我们可以让 &lt;code>DbContext&lt;/code> 继续注册为作用域生命周期，然后为视图模型注入一个 &lt;code>IServiceScopeFactory&lt;/code>，每次需要访问数据库时，就创建一个新的作用域，从而获得一个新的 &lt;code>DbContext&lt;/code> 实例。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">partial&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MainViewModel&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">ViewModelBase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">IServiceScopeFactory&lt;/span> &lt;span class="n">_scopeFactory&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [ObservableProperty]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="n">ObservableCollection&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Item&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_loadedItems&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">MainViewModel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">IServiceScopeFactory&lt;/span> &lt;span class="n">scopeFactory&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_scopeFactory&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">scopeFactory&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [RelayCommand(AllowConcurrent = true)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kd">async&lt;/span> &lt;span class="n">Task&lt;/span> &lt;span class="n">LoadDataAsync&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">scope&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">_scopeFactory&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CreateScope&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">dbContext&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">scope&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ServiceProvider&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetRequiredService&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">AppDbContext&lt;/span>&lt;span class="p">&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">items&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">dbContext&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToListAsync&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">LoadedItems&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">items&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">这里我们为 &lt;code>scope&lt;/code> 使用了 &lt;code>using&lt;/code> 语句，这样就可以确保在方法执行完成后，作用域会被正确地释放，从而避免内存泄漏的问题；另一方面，我们没有为 &lt;code>dbContext&lt;/code> 使用 &lt;code>using&lt;/code> 语句，因为它是由 &lt;code>scope&lt;/code> 管理的，而后者会在 &lt;code>using&lt;/code> 块结束时自动释放它所创建的所有服务实例。&lt;/div>
&lt;/div>
&lt;h2 id="方法三使用-idbcontextfactory">
方法三：使用 &lt;code>IDbContextFactory&lt;/code>
&lt;a href="#%e6%96%b9%e6%b3%95%e4%b8%89%e4%bd%bf%e7%94%a8-idbcontextfactory" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>或者我们还可以更简单一些，我们直接将 &lt;code>DbContext&lt;/code> 注册为瞬态生命周期，然后为视图模型注入一个 &lt;code>IDbContextFactory&amp;lt;AppDbContext&amp;gt;&lt;/code>，每次需要访问数据库时，就通过工厂创建一个新的 &lt;code>DbContext&lt;/code> 实例。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">partial&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MainViewModel&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">ViewModelBase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">IDbContextFactory&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">AppDbContext&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_dbContextFactory&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [ObservableProperty]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="n">ObservableCollection&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Item&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_loadedItems&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">MainViewModel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">IDbContextFactory&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">AppDbContext&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">dbContextFactory&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_dbContextFactory&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">dbContextFactory&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [RelayCommand(AllowConcurrent = true)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kd">async&lt;/span> &lt;span class="n">Task&lt;/span> &lt;span class="n">LoadDataAsync&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">dbContext&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">_dbContextFactory&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CreateDbContext&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">items&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">dbContext&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToListAsync&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">LoadedItems&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">items&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>如果使用这种方式，我们还要稍微修改一下服务的注册。这次我们就不用 &lt;code>AddDbContext&lt;/code> 了，而是使用 &lt;code>AddDbContextFactory&lt;/code> 来注册 &lt;code>DbContext&lt;/code> 工厂：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 注册 DbContext 工厂&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">builder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Services&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AddDbContextFactory&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">AppDbContext&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="n">options&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">options&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">UseSqlServer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">connectionString&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">使用工厂方法创建一个 &lt;code>DbContext&lt;/code> 的实例就类似于我们手动使用 &lt;code>new&lt;/code> 关键字创建一个对象一样。每次调用 &lt;code>CreateDbContext&lt;/code> 方法时，都会返回一个新的 &lt;code>DbContext&lt;/code> 实例，这样就可以避免线程安全和内存泄漏的问题。但因此我们也要手动处理它的生命周期，确保在使用完成后正确地释放它。所以上面我们对它使用了 &lt;code>using&lt;/code> 语句。&lt;/div>
&lt;/div>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>总的来说，在本地客户端应用程序中使用 EF Core 时，我们需要特别注意 &lt;code>DbContext&lt;/code> 的生命周期以及线程安全。只要解决了这两个问题，我们就可以放心地在客户端程序中使用 EF Core 来简化数据访问层的开发。&lt;/p></description></item><item><title>如何在类外移除类的事件订阅？</title><link>https://blog.coldwind.top/posts/how-to-remove-event-handler-outside-a-class.md/</link><pubDate>Thu, 22 Jan 2026 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/how-to-remove-event-handler-outside-a-class.md/</guid><description>&lt;p>某些时候，我们出于对第三方类库的定制需求，可能需要在类外移除该类的事件订阅。然而，事件本身就是一个封装良好的成员，直接访问和修改事件的订阅列表并不容易。不仅如此，为事件注册的方法可能还是私有的，这更是增加了难度。我们这次就来探讨如何通过反射机制实现这一目标。&lt;/p>
&lt;h2 id="简单情况">
简单情况
&lt;a href="#%e7%ae%80%e5%8d%95%e6%83%85%e5%86%b5" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>我们先来看一个最基本的例子。这里有一个 &lt;code>Demo&lt;/code> 类，它定义了一个事件 &lt;code>MyEvent&lt;/code>，并在构造函数中为该事件注册了一个事件处理器 &lt;code>MyEventHandler&lt;/code>，并且也是这个类的私有方法。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Demo&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">Demo&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">MyEvent&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">MyEventHandler&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">event&lt;/span> &lt;span class="n">EventHandler&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">MyEvent&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">void&lt;/span> &lt;span class="n">MyEventHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object?&lt;/span> &lt;span class="n">sender&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">EventArgs&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;MyEvent event triggered&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在这个情况下，我们可以借助反射来拿到 &lt;code>MyEvent&lt;/code> 事件的底层字段，然后将它置空，从而移除所有的事件订阅。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">demo&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Demo&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">eventField&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">typeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Demo&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">GetField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;MyEvent&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">BindingFlags&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Instance&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="n">BindingFlags&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">NonPublic&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">eventField&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SetValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">demo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样一来，&lt;code>MyEvent&lt;/code> 事件的所有订阅都被移除了。&lt;/p>
&lt;h2 id="事件声明在基类上">
事件声明在基类上
&lt;a href="#%e4%ba%8b%e4%bb%b6%e5%a3%b0%e6%98%8e%e5%9c%a8%e5%9f%ba%e7%b1%bb%e4%b8%8a" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>有时候，事件可能声明在类的基类上，比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">event&lt;/span> &lt;span class="n">EventHandler&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">MyEvent&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Demo&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">Base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">Demo&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">MyEvent&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">MyEventHandler&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">void&lt;/span> &lt;span class="n">MyEventHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object?&lt;/span> &lt;span class="n">sender&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">EventArgs&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;MyEvent event triggered&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这时候上面的方法就不奏效了。我们需要在反射时指定正确的类型：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">eventField&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">typeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Base&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">GetField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;MyEvent&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">BindingFlags&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Instance&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="n">BindingFlags&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">NonPublic&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">eventField&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SetValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">demo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>如果再复杂一点，我们甚至都不知道这个事件到底声明在哪个类上，这时候我们可以通过遍历继承链来查找：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="n">Type&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">type&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">typeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Demo&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">FieldInfo&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">eventField&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">type&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">eventField&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">type&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;MyEvent&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">BindingFlags&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Instance&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="n">BindingFlags&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">NonPublic&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">eventField&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">type&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">type&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">BaseType&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">eventField&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SetValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">demo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="移除特定的事件处理方法">
移除特定的事件处理方法
&lt;a href="#%e7%a7%bb%e9%99%a4%e7%89%b9%e5%ae%9a%e7%9a%84%e4%ba%8b%e4%bb%b6%e5%a4%84%e7%90%86%e6%96%b9%e6%b3%95" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>上面的方法都会移除所有的事件订阅。如果我们只想移除特定的方法怎么办？此时我们有两种方式。首先我们可以尝试获取事件的委托实例，然后从中移除特定的方法：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">eventField&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">typeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Demo&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">GetField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;MyEvent&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">BindingFlags&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Instance&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="n">BindingFlags&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">NonPublic&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">eventDelegate&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">MulticastDelegate&lt;/span>&lt;span class="p">?)&lt;/span>&lt;span class="n">eventField&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">demo&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">eventDelegate&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">handler&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">eventDelegate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetInvocationList&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">handler&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Method&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Name&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="s">&amp;#34;MyEventHandler&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">eventDelegate&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">MulticastDelegate&lt;/span>&lt;span class="p">?)&lt;/span>&lt;span class="n">Delegate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Remove&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">eventDelegate&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">handler&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">eventField&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SetValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">demo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">eventDelegate&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">C# 中事件是基于委托实现的。每个事件在底层都有一个与之关联的委托字段，这个字段保存了所有注册到该事件的处理方法。当事件被触发时，实际上是调用这个委托，从而依次调用所有注册的方法。具体来说，这个委托通常是一个多播委托（Multicast Delegate），它上面有一个方法列表，包含了所有注册的事件处理器。&lt;/div>
&lt;/div>
&lt;p>另一种方式是直接通过反射获取特定的方法，然后借助 &lt;code>Delegate&lt;/code> 创造这个方法的委托实例，再从事件中移除：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">methodInfo&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">typeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Demo&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">GetMethod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;MyEventHandler&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">BindingFlags&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Instance&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="n">BindingFlags&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">NonPublic&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">eventInfo&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">typeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Demo&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">GetEvent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;MyEvent&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">BindingFlags&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Instance&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="n">BindingFlags&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Public&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">handlerDelegate&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Delegate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CreateDelegate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">eventInfo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">EventHandlerType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">demo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">methodInfo&lt;/span>&lt;span class="p">!);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">eventInfo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">RemoveEventHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">demo&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">handlerDelegate&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>通过以上方法，我们就可以在类外成功地移除类的事件订阅了。&lt;/p>
&lt;h2 id="整理为通用方法">
整理为通用方法
&lt;a href="#%e6%95%b4%e7%90%86%e4%b8%ba%e9%80%9a%e7%94%a8%e6%96%b9%e6%b3%95" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>最后，结合上面的方法，我们可以得到两个通用的方法：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">ClearEventHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">eventName&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">handlerName&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Type&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">type&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">obj&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetType&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">FieldInfo&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">eventField&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">type&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">eventField&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">type&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">eventName&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">BindingFlags&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Instance&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="n">BindingFlags&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">NonPublic&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">eventField&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">type&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">type&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">BaseType&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">eventField&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">InvalidOperationException&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">$&amp;#34;Event field &amp;#39;{eventName}&amp;#39; not found.&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">eventDelegate&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">eventField&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">MulticastDelegate&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">eventDelegate&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">handler&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">eventDelegate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetInvocationList&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">handler&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Method&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Name&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="n">handlerName&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">eventDelegate&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">MulticastDelegate&lt;/span>&lt;span class="p">?)&lt;/span>&lt;span class="n">Delegate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Remove&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">eventDelegate&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">handler&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">eventField&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SetValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">eventDelegate&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">ClearAllEventHandlers&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">eventName&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Type&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">type&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">obj&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetType&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">FieldInfo&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">eventField&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">type&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">eventField&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">type&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">eventName&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">BindingFlags&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Instance&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="n">BindingFlags&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">NonPublic&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">eventField&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">type&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">type&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">BaseType&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">eventField&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">eventField&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SetValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这两个方法分别用于移除特定的事件处理方法和移除所有的事件订阅。并且它们都能处理事件声明在任意基类上的情况。&lt;/p></description></item><item><title>如何在 C# 中获取本机真实 IP 地址？</title><link>https://blog.coldwind.top/posts/how-to-get-real-local-ip-address/</link><pubDate>Thu, 15 Jan 2026 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/how-to-get-real-local-ip-address/</guid><description>&lt;p>获取本机 IP
地址听起来是一个非常简单的需求，但实际操作起来却并不容易。虚拟网卡、IPv6、APIPA
地址等因素会让我们获取到一大堆 IP，而如何从中筛选出真正想要的局域网 IP
才是我们要解决的问题。&lt;/p>
&lt;h2 id="常见的坑">
常见的坑
&lt;a href="#%e5%b8%b8%e8%a7%81%e7%9a%84%e5%9d%91" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;h3 id="1-获取到一堆-ip-地址">
1. 获取到一堆 IP 地址
&lt;a href="#1-%e8%8e%b7%e5%8f%96%e5%88%b0%e4%b8%80%e5%a0%86-ip-%e5%9c%b0%e5%9d%80" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>最直接的想法可能是使用 &lt;code>Dns.GetHostAddresses&lt;/code> 或 &lt;code>Dns.GetHostEntry&lt;/code>
来获取本机所有的 IP 地址：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">hostName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Dns&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetHostName&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">ipAddresses&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Dns&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetHostEntry&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hostName&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">AddressList&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">ip&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">ipAddresses&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ip&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>运行后你可能会看到类似这样的输出：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">192.168.1.100
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">169.254.123.45
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">172.17.0.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">10.0.75.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">192.168.56.1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">fe80::1234:5678:abcd:ef01%12
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">::1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这么多 IP 地址，到底哪个才是我们想要的真实局域网 IP？&lt;/p>
&lt;h3 id="2-虚拟网卡的干扰">
2. 虚拟网卡的干扰
&lt;a href="#2-%e8%99%9a%e6%8b%9f%e7%bd%91%e5%8d%a1%e7%9a%84%e5%b9%b2%e6%89%b0" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>现代计算机上通常会存在多种类型的网络适配器：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>物理网卡&lt;/strong>：真实的以太网卡或 Wi-Fi 适配器&lt;/li>
&lt;li>&lt;strong>虚拟网卡&lt;/strong>：
&lt;ul>
&lt;li>VMware、VirtualBox、Hyper-V 等虚拟机软件创建的虚拟网卡&lt;/li>
&lt;li>Docker Desktop 创建的虚拟网卡（如 vEthernet）&lt;/li>
&lt;li>VPN 软件创建的虚拟适配器&lt;/li>
&lt;li>WSL2 创建的虚拟网卡&lt;/li>
&lt;li>蓝牙适配器&lt;/li>
&lt;li>回环地址（Loopback）&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>这些虚拟网卡都会有自己的 IP 地址，导致我们获取到一大堆无用的地址。&lt;/p>
&lt;h3 id="3-ipv4-vs-ipv6">
3. IPv4 vs IPv6
&lt;a href="#3-ipv4-vs-ipv6" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>除了虚拟网卡的问题，还有 IPv4 和 IPv6 的区别：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>IPv4&lt;/strong>：如 &lt;code>192.168.1.100&lt;/code>（我们通常想要的）&lt;/li>
&lt;li>&lt;strong>IPv6&lt;/strong>：如 &lt;code>fe80::1234:5678:abcd:ef01%12&lt;/code>（链路本地地址）&lt;/li>
&lt;li>&lt;strong>IPv6 本地回环&lt;/strong>：&lt;code>::1&lt;/code>（相当于 IPv4 的 &lt;code>127.0.0.1&lt;/code>）&lt;/li>
&lt;/ul>
&lt;p>在大多数局域网场景中，我们想要的是 IPv4 地址。&lt;/p>
&lt;h2 id="解决方案">
解决方案
&lt;a href="#%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;h3 id="方法一使用-networkinterface-获取">
方法一：使用 NetworkInterface 获取
&lt;a href="#%e6%96%b9%e6%b3%95%e4%b8%80%e4%bd%bf%e7%94%a8-networkinterface-%e8%8e%b7%e5%8f%96" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>这个方法通过 &lt;code>NetworkInterface&lt;/code>
获取所有网络接口，过滤出正在运行的网卡，排除虚拟网卡，然后根据接口索引排序选择最佳
IP。下面的例子中排除了 VMware 虚拟网卡：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="kt">string?&lt;/span> &lt;span class="n">GetBestIPByMetric&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">bestIp&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">NetworkInterface&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetAllNetworkInterfaces&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Where&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OperationalStatus&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="n">OperationalStatus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Up&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Where&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Description&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToLower&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;vmware&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1">// 排除 VMware 虚拟网卡&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">SelectMany&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetIPProperties&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">UnicastAddresses&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Where&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Address&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AddressFamily&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Net&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Sockets&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AddressFamily&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">InterNetwork&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Select&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="k">new&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">IP&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Address&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToString&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Description&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Description&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 获取网卡的 IPv4 接口指标&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Metric&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetIPProperties&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">GetIPv4Properties&lt;/span>&lt;span class="p">()?.&lt;/span>&lt;span class="n">Index&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">OrderBy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Metric&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 索引通常反映了绑定顺序&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">FirstOrDefault&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">bestIp&lt;/span>&lt;span class="p">?.&lt;/span>&lt;span class="n">IP&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这个方法的优点是精确可控，可以根据需求添加过滤条件，且不依赖路由表。缺点是需要手动维护虚拟网卡的排除列表，不同虚拟网卡的名称和描述可能不同。&lt;/p>
&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">接口索引（Index）通常反映了网卡的绑定顺序和优先级，索引越小优先级越高。通过
&lt;code>GetIPv4Properties().Index&lt;/code> 可以获取这个值。&lt;/div>
&lt;/div>
&lt;h3 id="方法二使用-socket-连接外部地址">
方法二：使用 Socket 连接外部地址
&lt;a href="#%e6%96%b9%e6%b3%95%e4%ba%8c%e4%bd%bf%e7%94%a8-socket-%e8%bf%9e%e6%8e%a5%e5%a4%96%e9%83%a8%e5%9c%b0%e5%9d%80" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>这是一个非常经典且巧妙的方法。通过创建一个 UDP Socket 并&amp;quot;连接&amp;quot;到外部地址（如
Google DNS 的 8.8.8.8），让操作系统根据路由表自动选择最合适的本地 IP。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="kt">string?&lt;/span> &lt;span class="n">GetLocalIp&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="nn">Socket&lt;/span> &lt;span class="n">socket&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Socket&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">AddressFamily&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">InterNetwork&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">SocketType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Dgram&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 这里使用一个伪造的外部地址。&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 即使断网，系统也会根据路由表返回最匹配的物理网卡 IP&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">socket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Connect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;8.8.8.8&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">65530&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">endPoint&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">socket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">LocalEndPoint&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">IPEndPoint&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">endPoint&lt;/span>&lt;span class="p">?.&lt;/span>&lt;span class="n">Address&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToString&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">catch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 如果没有任何网卡连接，会进入这里&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s">&amp;#34;127.0.0.1&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">这里使用的是 UDP 协议（&lt;code>SocketType.Dgram&lt;/code>），&lt;code>Connect&lt;/code>
方法只是设置默认目标地址，&lt;strong>不会真正发送数据包&lt;/strong>。因此即使断网也能正常工作，系统会根据路由表返回最匹配的本地
IP。&lt;/div>
&lt;/div>
&lt;p>这个方法代码最简洁，能自动避开虚拟网卡。但如果局域网与外网完全隔离，路由表中没有相应路由时可能失效。&lt;/p>
&lt;h3 id="方法三使用-wmi-查询物理网卡">
方法三：使用 WMI 查询物理网卡
&lt;a href="#%e6%96%b9%e6%b3%95%e4%b8%89%e4%bd%bf%e7%94%a8-wmi-%e6%9f%a5%e8%af%a2%e7%89%a9%e7%90%86%e7%bd%91%e5%8d%a1" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>这个方法利用 Windows Management Instrumentation (WMI)
来查询系统中标记为物理适配器的网卡，然后与 &lt;code>NetworkInterface&lt;/code> 的 DeviceID
进行匹配，从而准确过滤出物理网卡的 IP。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">GetRealIP&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 使用 WMI 查询物理网卡&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">searcher&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ManagementObjectSearcher&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;SELECT * FROM Win32_NetworkAdapter WHERE PhysicalAdapter = True&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">physicalIds&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">searcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Get&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Cast&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">ManagementBaseObject&lt;/span>&lt;span class="p">&amp;gt;()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Select&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s">&amp;#34;DeviceID&amp;#34;&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="n">ToString&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">ToList&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">interfaces&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">NetworkInterface&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetAllNetworkInterfaces&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">ni&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">interfaces&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 匹配物理网卡 ID 并且状态为 Up&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">physicalIds&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ni&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Id&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">ni&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OperationalStatus&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="n">OperationalStatus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Up&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">props&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">ni&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetIPProperties&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">ipv4&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">props&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">UnicastAddresses&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">FirstOrDefault&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Address&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AddressFamily&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Net&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Sockets&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AddressFamily&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">InterNetwork&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ipv4&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 排除掉 169.254.x.x (自动配置地址)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ipv4&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Address&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToString&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">StartsWith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;169.254&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">$&amp;#34;网卡名称: {ni.Description}&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">$&amp;#34;真实局域网 IP: {ipv4.Address}&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这个方法从准确性来说是最可靠的，能从操作系统底层识别物理网卡。但它有明显的局限性：&lt;/p>
&lt;ul>
&lt;li>需要引用 &lt;code>System.Management&lt;/code> NuGet 包&lt;/li>
&lt;li>只能在 Windows 平台使用，不支持跨平台&lt;/li>
&lt;li>WMI 查询相对较慢，性能敏感场景需要缓存结果&lt;/li>
&lt;/ul>
&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">&lt;code>169.254.x.x&lt;/code> 是 APIPA（Automatic Private IP
Addressing）地址段，当 DHCP 服务器不可用时，Windows
会自动分配这个范围的地址。这通常不是我们想要的真实局域网地址。&lt;/div>
&lt;/div>
&lt;h3 id="方法四networkinterface-评分机制终极方案">
方法四：NetworkInterface 评分机制（终极方案）
&lt;a href="#%e6%96%b9%e6%b3%95%e5%9b%9bnetworkinterface-%e8%af%84%e5%88%86%e6%9c%ba%e5%88%b6%e7%bb%88%e6%9e%81%e6%96%b9%e6%a1%88" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>兜兜转转尝试了各种方法后，发现还是回到 &lt;code>NetworkInterface&lt;/code>
最简单且有效。下面这个方案相当于 &lt;a class="link" href="#%e6%96%b9%e6%b3%95%e4%b8%80%e4%bd%bf%e7%94%a8-networkinterface-%e8%8e%b7%e5%8f%96" >方法一&lt;/a>
的增强版，通过更全面的过滤条件和评分机制来选择最佳 IP 地址：&lt;/p>
&lt;ul>
&lt;li>排除更多种类的虚拟网卡（Clash、ZeroTier、Tailscale、WireGuard、VMware、Docker
等）&lt;/li>
&lt;li>排除特殊 IP 段（Docker 的 172.16-31.x.x、网络基准测试的 198.18.x.x、APIPA 的
169.254.x.x）&lt;/li>
&lt;li>使用评分系统综合评估：有网关 +100 分，以太网/无线网卡 +50
分，知名厂商（Intel、Realtek 等）+30 分&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">GetRealLocalIP&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">allInterfaces&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">NetworkInterface&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetAllNetworkInterfaces&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">candidates&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;(&lt;/span>&lt;span class="n">IPAddress&lt;/span> &lt;span class="n">Ip&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Score&lt;/span>&lt;span class="p">)&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">ni&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">allInterfaces&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ni&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OperationalStatus&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="n">OperationalStatus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Up&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ni&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">NetworkInterfaceType&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="n">NetworkInterfaceType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Loopback&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ni&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">NetworkInterfaceType&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="n">NetworkInterfaceType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Tunnel&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">string&lt;/span> &lt;span class="n">desc&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">ni&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Description&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToLower&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">string&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">ni&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Name&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToLower&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">desc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;wintun&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">||&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;clash&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">||&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;virtual&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">||&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;vmware&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">||&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;vbox&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">||&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;hyper-v&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">||&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;zerotier&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">||&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;tailscale&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">||&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;wireguard&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">||&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">desc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;docker&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">||&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;vethernet&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">||&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;wsl&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">ipProps&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">ni&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetIPProperties&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">ipv4Addrs&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">ipProps&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">UnicastAddresses&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Where&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Address&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AddressFamily&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="n">AddressFamily&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">InterNetwork&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">addr&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">ipv4Addrs&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">string&lt;/span> &lt;span class="n">ipStr&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">addr&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Address&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToString&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ipStr&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">StartsWith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;169.254&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ipStr&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">StartsWith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;198.18.&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ipStr&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">StartsWith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;172.&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">parts&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">ipStr&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sc">&amp;#39;.&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">parts&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="m">4&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">TryParse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">parts&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="k">out&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">secondOctet&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">secondOctet&lt;/span> &lt;span class="p">&amp;gt;=&lt;/span> &lt;span class="m">16&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">secondOctet&lt;/span> &lt;span class="p">&amp;lt;=&lt;/span> &lt;span class="m">31&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">score&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ipProps&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GatewayAddresses&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Any&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">g&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="n">g&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Address&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToString&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">Equals&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;0.0.0.0&amp;#34;&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">score&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="m">100&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ni&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">NetworkInterfaceType&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="n">NetworkInterfaceType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Ethernet&lt;/span> &lt;span class="p">||&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ni&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">NetworkInterfaceType&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="n">NetworkInterfaceType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Wireless80211&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">score&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="m">50&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">desc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;intel&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">||&lt;/span> &lt;span class="n">desc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;realtek&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">||&lt;/span> &lt;span class="n">desc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;atheros&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">||&lt;/span> &lt;span class="n">desc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;broadcom&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">score&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="m">30&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">candidates&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="n">addr&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Address&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">score&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">candidates&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Count&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s">&amp;#34;192.168.1.100&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">candidates&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OrderByDescending&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">c&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Score&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">First&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">Ip&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToString&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这个版本覆盖了几乎所有常见场景，通过多维度评分确保选择的是真正在使用的物理网卡
IP。评分机制比简单的索引排序或依赖路由表更智能，能适应各种复杂网络环境。&lt;/p>
&lt;div class="notice warning">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-triangle" aria-hidden="true">&lt;/i>Warning
&lt;/div>
&lt;div class="notice-content">虽然这个方法提供了最大的灵活性和准确性，但代码较长，维护成本相对较高。需要根据实际遇到的虚拟网卡类型持续更新过滤列表。&lt;/div>
&lt;/div>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>获取本机真实 IP 地址看似简单，实则需要处理虚拟网卡、特殊 IP
段、多网卡等复杂情况。本文介绍了四种方法，各有优劣：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>方法&lt;/th>
&lt;th>优点&lt;/th>
&lt;th>缺点&lt;/th>
&lt;th>适用场景&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>方法一（基础 NetworkInterface）&lt;/td>
&lt;td>可控性强，代码相对简单&lt;/td>
&lt;td>需要手动维护排除列表&lt;/td>
&lt;td>一般场景&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>方法二（Socket 连接）&lt;/td>
&lt;td>代码最简洁，自动选择&lt;/td>
&lt;td>完全隔离网络可能失效&lt;/td>
&lt;td>快速实现&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>方法三（WMI 查询）&lt;/td>
&lt;td>最准确，系统层面识别&lt;/td>
&lt;td>仅限 Windows，性能较慢&lt;/td>
&lt;td>Windows 专用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>方法四（评分机制）&lt;/td>
&lt;td>最灵活准确，覆盖全面&lt;/td>
&lt;td>代码较长，维护成本高&lt;/td>
&lt;td>复杂网络环境&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>大家可以根据自己的实际需求选择合适的方法，或者结合多种方法以获得最佳效果。&lt;/p></description></item><item><title>如何在不使用 .gitignore 的情况下忽略代码仓库中的文件或文件夹</title><link>https://blog.coldwind.top/posts/ignore-file-in-repo-without-using-gitignore/</link><pubDate>Thu, 06 Nov 2025 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/ignore-file-in-repo-without-using-gitignore/</guid><description>&lt;p>我们在管理代码仓库时，有时候会在项目中引入一些额外的文件或文件夹，常见的比如我们想临时测试效果的 &lt;code>test&lt;/code> 文件，或 &lt;code>.vscode&lt;/code>、&lt;code>.idea&lt;/code>、&lt;code>.github&lt;/code> 等配置文件夹。这些文件或文件夹可能不适合被提交到版本控制系统中，但我们又不想使用 &lt;code>.gitignore&lt;/code> 文件来忽略它们，因为这样我们就需要将 &lt;code>.gitignore&lt;/code> 文件也提交到仓库中，影响其他协作者（或者让其他人察觉到你可能在本地仓库做了什么 :)&lt;/p>
&lt;p>有一个笨方法，就是修改本地的 .gitignore 文件，但是不提交它。这样虽然可以达到忽略文件的目的，但拉取代码时，如果其他协作者更新了 &lt;code>.gitignore&lt;/code> 文件，你的本地修改就会被覆盖，导致忽略设置失效。或者者你需要频繁地手动合并 &lt;code>.gitignore&lt;/code> 文件，增加了维护成本。&lt;/p>
&lt;p>那么，有没有一种方法可以在不使用 &lt;code>.gitignore&lt;/code> 的情况下，忽略这些文件或文件夹呢？答案是肯定的。下面我们就简单介绍几种方式。&lt;/p>
&lt;h2 id="使用-git-update-index---assume-unchanged">
使用 &lt;code>git update-index --assume-unchanged&lt;/code>
&lt;a href="#%e4%bd%bf%e7%94%a8-git-update-index---assume-unchanged" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>Git 提供了一个命令 &lt;code>git update-index --assume-unchanged &amp;lt;file&amp;gt;&lt;/code>，可以让 Git 忽略对指定文件的更改。这样，即使你在本地修改了该文件，Git 也不会将其标记为已更改状态。&lt;/p>
&lt;p>例如，如果你有一个名为 &lt;code>test.py&lt;/code> 的文件，你可以运行以下命令：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git update-index --assume-unchanged test.py
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后就会发现，它不会出现在 &lt;code>git status&lt;/code> 的输出中了。&lt;/p>
&lt;p>如果你想恢复对该文件的跟踪，可以使用以下命令：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git update-index --no-assume-unchanged test.py
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但是这个方法有一个缺点，就是它不能使用通配符来忽略多个文件或文件夹，你需要对每个文件单独执行该命令。&lt;/p>
&lt;p>此外，假如这个文件已经被提交到了仓库中，并且其他协作者修改了它后被拉取到了本地，那么你可能无法察觉到这些更改。&lt;/p>
&lt;h2 id="使用-gitinfoexclude">
使用 &lt;code>.git/info/exclude&lt;/code>
&lt;a href="#%e4%bd%bf%e7%94%a8-gitinfoexclude" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>另一个方法是使用 Git 仓库中的 &lt;code>.git/info/exclude&lt;/code> 文件。这个文件的作用类似于 &lt;code>.gitignore&lt;/code>，但它是本地的，不会被提交到远程仓库。而且它的语法规则是和 &lt;code>.gitignore&lt;/code> 一样的，支持使用通配符。&lt;/p>
&lt;p>你可以在 &lt;code>.git/info/exclude&lt;/code> 文件中添加你想忽略的文件或文件夹路径。例如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">test.*
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">.vscode/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">.idea/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">.github/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[Pp]ublish/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这个方式就比较适合忽略多个文件或文件夹，而且不会影响其他协作者。&lt;/p>
&lt;p>另外，&lt;code>.git&lt;/code> 文件夹通常是隐藏的，并且在 VS Code 等编辑器中默认也是隐藏的。如果你需要编辑 &lt;code>.git/info/exclude&lt;/code> 文件，可以在编辑器中打开隐藏文件夹，或者使用命令行编辑器进行修改。以 VS Code 为例，可以打开终端，进入项目根目录，然后运行以下命令：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">code .git/info/exclude
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样就可以直接在 VS Code 中编辑该文件了。&lt;/p></description></item><item><title>分享一些针对 WPF 开发者的 Avalonia 开发技巧</title><link>https://blog.coldwind.top/posts/avalonia-tips-for-wpf-developers/</link><pubDate>Thu, 23 Oct 2025 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/avalonia-tips-for-wpf-developers/</guid><description>&lt;p>Avalonia 在设计上借鉴了 WPF 的许多概念，开发体验来说也有很多相似之处。比如 XAML 语法、数据绑定、样式与模板等等，这使得 WPF 开发者能够较快上手 Avalonia。然而，Avalonia 也有其独特之处和最佳实践。如果对这些不够了解，WPF 开发者可能会将一些 WPF 的习惯直接套用到 Avalonia 上，导致代码不够高效或难以维护。&lt;/p>
&lt;p>本文将分享一些针对 WPF 开发者在使用 Avalonia 时的实用建议，帮助大家更好地适应和利用 Avalonia 的特性，从而提升开发效率和应用性能。&lt;/p>
&lt;h2 id="布局控件的改良">
布局控件的改良
&lt;a href="#%e5%b8%83%e5%b1%80%e6%8e%a7%e4%bb%b6%e7%9a%84%e6%94%b9%e8%89%af" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;h3 id="子控件间距">
子控件间距
&lt;a href="#%e5%ad%90%e6%8e%a7%e4%bb%b6%e9%97%b4%e8%b7%9d" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>Avalonia 为一些常用的布局控件提供了方便好用的属性。其中最方便的就是与 &lt;code>Spacing&lt;/code> 相关的一些属性。在 WPF 中，如果想让控件之间有间距，通常需要使用 &lt;code>Margin&lt;/code> 属性，导致代码看起来非常冗长：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;StackPanel&lt;/span> &lt;span class="na">Orientation=&lt;/span>&lt;span class="s">&amp;#34;Horizontal&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Button 1&amp;#34;&lt;/span> &lt;span class="na">Margin=&lt;/span>&lt;span class="s">&amp;#34;0,0,10,0&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Button 2&amp;#34;&lt;/span> &lt;span class="na">Margin=&lt;/span>&lt;span class="s">&amp;#34;0,0,10,0&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Button 3&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但是在 Avalonia 中，可以直接使用 &lt;code>Spacing&lt;/code> 属性来设置控件之间的间距，使代码更加简洁：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;StackPanel&lt;/span> &lt;span class="na">Orientation=&lt;/span>&lt;span class="s">&amp;#34;Horizontal&amp;#34;&lt;/span> &lt;span class="na">Spacing=&lt;/span>&lt;span class="s">&amp;#34;10&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Button 1&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Button 2&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Button 3&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>除了 &lt;code>StackPanel&lt;/code>，&lt;code>WrapPanel&lt;/code>、&lt;code>Grid&lt;/code> 和 &lt;code>UniformGrid&lt;/code> 也支持 &lt;code>Spacing&lt;/code> 属性。具体来说：&lt;/p>
&lt;ul>
&lt;li>&lt;code>StackPanel&lt;/code>：
&lt;ul>
&lt;li>&lt;code>Spacing&lt;/code>：设置子项之间的间距&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;code>WrapPanel&lt;/code>：
&lt;ul>
&lt;li>&lt;code>ItemSpacing&lt;/code>：设置子项之间的间距&lt;/li>
&lt;li>&lt;code>LineSpacing&lt;/code>：设置行之间的间距&lt;/li>
&lt;li>&lt;code>ItemsAlignment&lt;/code>：设置整行（或列，取决于方向）的对齐方式&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;code>Grid&lt;/code> 与 &lt;code>UniformGrid&lt;/code>：
&lt;ul>
&lt;li>&lt;code>RowSpacing&lt;/code>：设置行之间的间距&lt;/li>
&lt;li>&lt;code>ColumnSpacing&lt;/code>：设置列之间的间距&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>使用这些属性可以让布局代码更加简洁易读，避免了大量的 &lt;code>Margin&lt;/code> 设置。&lt;/p>
&lt;h3 id="grid-控件">
&lt;code>Grid&lt;/code> 控件
&lt;a href="#grid-%e6%8e%a7%e4%bb%b6" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>在 Avalonia 中，&lt;code>Grid&lt;/code> 也迎来了一些开发体验的优化。除了上面提到的 &lt;code>RowSpacing&lt;/code> 和 &lt;code>ColumnSpacing&lt;/code> 属性外，&lt;code>Grid&lt;/code> 还支持 &lt;code>RowDefinitions&lt;/code> 和 &lt;code>ColumnDefinitions&lt;/code> 的简化语法。我们现在可以用字符串的形式来快速定义行和列：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Grid&lt;/span> &lt;span class="na">ColumnDefinitions=&lt;/span>&lt;span class="s">&amp;#34;Auto,*,100&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- WPF 的做法 --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Grid.RowDefinitions&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;RowDefinition&lt;/span> &lt;span class="na">Height=&lt;/span>&lt;span class="s">&amp;#34;Auto&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;RowDefinition&lt;/span> &lt;span class="na">Height=&lt;/span>&lt;span class="s">&amp;#34;*&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;RowDefinition&lt;/span> &lt;span class="na">Height=&lt;/span>&lt;span class="s">&amp;#34;100&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Grid.RowDefinitions&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">如果需要操作这些 &lt;code>Definition&lt;/code>，比如在运行时动态调整它们的可见性、尺寸等，那我们仍然需要使用传统的方式来定义。&lt;/div>
&lt;/div>
&lt;p>此外，如果我们的布局要求非常简单，比如并不会用到行与列，只是简单地将子控件堆叠在一起，最多是借助它们的 &lt;code>Alignment&lt;/code> 属性来调整位置，那么我们可以使用更加轻量的 &lt;code>Panel&lt;/code> 控件来替代 &lt;code>Grid&lt;/code>，以提升性能。而 WPF 因为缺乏这样的轻量级容器，往往会过度使用 &lt;code>Grid&lt;/code>，导致性能下降。也因此，不少第三方控件库提供了诸如 &lt;code>SimplePanel&lt;/code> 之类的轻量级容器来弥补这一缺陷。&lt;/p>
&lt;h2 id="集合类型">
集合类型
&lt;a href="#%e9%9b%86%e5%90%88%e7%b1%bb%e5%9e%8b" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在 WPF 中，我们都知道，如果想要让前台的 &lt;code>ItemsControl&lt;/code>（及其子类，如 &lt;code>ListBox&lt;/code>、&lt;code>ComboBox&lt;/code> 等）能够响应集合的变化，我们需要使用 &lt;code>ObservableCollection&amp;lt;T&amp;gt;&lt;/code> 作为数据源，因为它实现了 &lt;code>INotifyCollectionChanged&lt;/code> 接口，能够在集合发生变化时通知 UI 更新。&lt;/p>
&lt;p>而在 Avalonia 中，我们可以考虑使用它提供的 &lt;code>AvaloniaList&amp;lt;T&amp;gt;&lt;/code> 作为集合类型。简单来说，&lt;code>AvaloniaList&amp;lt;T&amp;gt;&lt;/code> 提供了以下几条额外的功能：&lt;/p>
&lt;ol>
&lt;li>可以设置 &lt;code>ResetBehavior&lt;/code> 属性来控制集合被清空时触发的是 &lt;code>NotifyCollectionChangedAction.Reset&lt;/code> 还是 &lt;code>Remove&lt;/code>：&lt;code>Reset&lt;/code> 仅通知，但事件参数不包含具体删除了哪些元素，而 &lt;code>Remove&lt;/code> 则会包含被删除的元素列表&lt;/li>
&lt;li>提供了 &lt;code>Validate&lt;/code> 方法，可以在添加元素时进行验证&lt;/li>
&lt;li>提供了 &lt;code>AddRange&lt;/code> 和 &lt;code>RemoveRange&lt;/code> 方法，可以一次性添加或移除多个元素&lt;/li>
&lt;/ol>
&lt;p>这些新功能可以说是显著提升了 &lt;code>ObservableCollection&amp;lt;T&amp;gt;&lt;/code> 的使用体验。&lt;/p>
&lt;p>Avalonia 还提供了 &lt;code>AvaloniaDictionary&amp;lt;,&amp;gt;&lt;/code>，它是一个具备通知功能的字典类型。WPF 因为缺乏类似 &lt;code>ObservableDictionary&amp;lt;,&amp;gt;&lt;/code> 的类型，往往需要开发者自行实现，而 Avalonia 则直接提供了现成的解决方案供我们使用。&lt;/p>
&lt;p>此外，对于 &lt;code>DataGrid&lt;/code> 控件，Avalonia 还提供了 &lt;code>DataGridCollectionView&lt;/code>，它是一个支持排序、过滤、分组等功能的集合视图类型，可以大大提高数据展示的灵活性。在 11.3.x 版本的 Avalonia 中，它被迁移到了 &lt;code>Avalonia.Controls.DataGrid&lt;/code> 包中，方便我们单独引用。&lt;/p>
&lt;h2 id="值转换器">
值转换器
&lt;a href="#%e5%80%bc%e8%bd%ac%e6%8d%a2%e5%99%a8" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在 WPF 中，我们常常需要与值转换器（&lt;code>IValueConverter&lt;/code>）打交道，以便在数据绑定时对数据进行转换。WPF 原生几乎只提供了一个我们用得上的转换器——&lt;code>BooleanToVisibilityConverter&lt;/code>。其他的转换器通常需要我们自己实现。这同时也因为 WPF 的绑定语法不够灵活，导致连一个简单的布尔值取反都需要我们自己写转换器。&lt;/p>
&lt;p>而在 Avalonia 中，情况则大不相同。甚至可以说，在遇到看似需要我们写值转换器的场景时，我们应该先考虑是否可以通过 Avalonia 提供的内置功能来实现，并且很多时候都是可以的。&lt;/p>
&lt;h3 id="内置转换器">
内置转换器
&lt;a href="#%e5%86%85%e7%bd%ae%e8%bd%ac%e6%8d%a2%e5%99%a8" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>Avalonia 提供了丰富的内置值转换器，涵盖了常见的转换需求。比如：&lt;/p>
&lt;ul>
&lt;li>&lt;code>BoolConverters&lt;/code>
&lt;ul>
&lt;li>提供了一些多值转换器（&lt;code>IMultiValueConverter&lt;/code>），如 &lt;code>AndConverter&lt;/code>、&lt;code>OrConverter&lt;/code> 等，可用于 &lt;code>MultiBinding&lt;/code>&lt;/li>
&lt;li>提供了布尔值的取反转换器 &lt;code>NotConverter&lt;/code>，但通常可以用绑定表达式的特殊语法来实现&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;code>StringConverters&lt;/code>
&lt;ul>
&lt;li>提供了一些与字符串有关的转换器，如 &lt;code>IsNullOrEmpty&lt;/code>、&lt;code>IsNullOrWhiteSpace&lt;/code> 等&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;code>ObjectConverters&lt;/code>
&lt;ul>
&lt;li>提供了一些与对象有关的转换器，如 &lt;code>IsNull&lt;/code>、&lt;code>IsNotNull&lt;/code>、&lt;code>Equal&lt;/code>、&lt;code>NotEqual&lt;/code> 等&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>运用这些内置的转换器，我们可以轻易实现很多常见的需求，比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="c">&amp;lt;!-- 只有当所有开关都打开时，提交按钮才可用 --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ToggleSwitch&lt;/span> &lt;span class="na">x:Name=&lt;/span>&lt;span class="s">&amp;#34;Toggle1&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ToggleSwitch&lt;/span> &lt;span class="na">x:Name=&lt;/span>&lt;span class="s">&amp;#34;Toggle2&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ToggleSwitch&lt;/span> &lt;span class="na">x:Name=&lt;/span>&lt;span class="s">&amp;#34;Toggle3&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Submit&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button.IsEnabled&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;MultiBinding&lt;/span> &lt;span class="na">Converter=&lt;/span>&lt;span class="s">&amp;#34;{x:Static BoolConverters.AndConverter}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Binding&lt;/span> &lt;span class="na">ElementName=&lt;/span>&lt;span class="s">&amp;#34;Toggle1&amp;#34;&lt;/span> &lt;span class="na">Path=&lt;/span>&lt;span class="s">&amp;#34;IsChecked&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Binding&lt;/span> &lt;span class="na">ElementName=&lt;/span>&lt;span class="s">&amp;#34;Toggle2&amp;#34;&lt;/span> &lt;span class="na">Path=&lt;/span>&lt;span class="s">&amp;#34;IsChecked&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Binding&lt;/span> &lt;span class="na">ElementName=&lt;/span>&lt;span class="s">&amp;#34;Toggle3&amp;#34;&lt;/span> &lt;span class="na">Path=&lt;/span>&lt;span class="s">&amp;#34;IsChecked&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/MultiBinding&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Button.IsEnabled&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Button&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ListBox&lt;/span> &lt;span class="na">x:Name=&lt;/span>&lt;span class="s">&amp;#34;MyListBox&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="na">IsVisible=&lt;/span>&lt;span class="s">&amp;#34;{Binding #MyListBox.SelectedItem, Converter={x:Static ObjectConverters.IsNotNull}}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> An item is selected
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/TextBlock&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="绑定表达式">
绑定表达式
&lt;a href="#%e7%bb%91%e5%ae%9a%e8%a1%a8%e8%be%be%e5%bc%8f" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>Avalonia 的绑定表达式语法也比 WPF 更加灵活强大。我们可以在绑定路径中直接使用一些特殊的语法来实现简单的转换需求，而无需借助值转换器。&lt;/p>
&lt;p>比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="c">&amp;lt;!-- 布尔值取反 --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ToggleSwitch&lt;/span> &lt;span class="na">x:Name=&lt;/span>&lt;span class="s">&amp;#34;MyToggle&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="na">IsVisible=&lt;/span>&lt;span class="s">&amp;#34;{Binding #MyToggle.IsChecked, Path=!}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> The toggle is off
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/TextBlock&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&amp;lt;!-- 字符串不为空 --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="na">x:Name=&lt;/span>&lt;span class="s">&amp;#34;MyTextBox&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="na">IsVisible=&lt;/span>&lt;span class="s">&amp;#34;{Binding !!#MyTextBox.Text}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Text is not empty
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/TextBlock&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>通过这些内置的转换器和灵活的绑定表达式语法，我们可以大大减少自定义值转换器的编写，从而简化代码，提高开发效率。&lt;/p>
&lt;h3 id="函数值转换器">
函数值转换器
&lt;a href="#%e5%87%bd%e6%95%b0%e5%80%bc%e8%bd%ac%e6%8d%a2%e5%99%a8" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>如果上面的这些方式还不能满足，那么或许依然不必急于去写值转换器。Avalonia 还提供了函数值转换器（&lt;code>FuncValueConverter&lt;/code>），它允许我们快速地在后台代码中定义一个转换函数，并将其直接用于绑定中，而无需创建一个完整的转换器类。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MyViewModel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="n">FuncValueConverter&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">bool&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">StringToBoolConverter&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">str&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IsNullOrEmpty&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">str&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="na">x:Name=&lt;/span>&lt;span class="s">&amp;#34;MyTextBox&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="na">IsVisible=&lt;/span>&lt;span class="s">&amp;#34;{Binding #MyTextBox.Text, Converter={x:Static local:MyViewModel.StringToBoolConverter}}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Text is not empty
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/TextBlock&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>通过这种方式，我们可以快速地实现一些简单的转换逻辑，而无需编写冗长的转换器类，从而提高开发效率。如果希望传入参数（&lt;code>ConverterParameter&lt;/code>），它还有一个 &lt;code>FuncValueConverter&amp;lt;TIn, TParam, TOut&amp;gt;&lt;/code> 的重载版本，可以满足这一需求。此外，我们还有 &lt;code>FuncMultiValueConverter&lt;/code> 可供使用，适用于多值绑定的场景。&lt;/p>
&lt;p>但需要注意，这种方式存在一定局限性：它只支持正向转换（&lt;code>Convert&lt;/code> 方法），不支持反向转换（&lt;code>ConvertBack&lt;/code> 方法）。因此，如果需要更复杂的转换逻辑，仍然需要编写完整的值转换器类。&lt;/p>
&lt;h2 id="xmlns-命名空间">
xmlns 命名空间
&lt;a href="#xmlns-%e5%91%bd%e5%90%8d%e7%a9%ba%e9%97%b4" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在 WPF 中，如果我们想要引入一个程序集中的控件或类型，通常需要在 XAML 文件的开头使用 &lt;code>xmlns&lt;/code> 声明一个命名空间，并指定程序集名称：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="na">xmlns:md=&lt;/span>&lt;span class="s">&amp;#34;http://materialdesigninxaml.net/winfx/xaml/themes&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:local=&lt;/span>&lt;span class="s">&amp;#34;clr-namespace:MyApp.Controls&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:classlib=&lt;/span>&lt;span class="s">&amp;#34;clr-namespace:ClassLibrary.Controls;assembly=ClassLibrary&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>如果在当前程序集，那么我们只需要 &lt;code>clr-namespace&lt;/code> 即可；如果是其他程序集，则需要加上 &lt;code>assembly&lt;/code> 部分。&lt;/p>
&lt;p>这样的方式在 Avalonia 中同样适用，但 Avalonia 还提供了更加简洁的 &lt;code>using&lt;/code> 语法，允许我们直接使用程序集名称来引入命名空间：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="na">xmlns:md=&lt;/span>&lt;span class="s">&amp;#34;http://materialdesigninxaml.net/winfx/xaml/themes&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:local=&lt;/span>&lt;span class="s">&amp;#34;using:MyApp.Controls&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:classlib=&lt;/span>&lt;span class="s">&amp;#34;using:ClassLibrary.Controls&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>另外，Avalonia 的默认 &lt;code>x&lt;/code> 命名空间也为我们提供了不少便利。在 WPF 中，如果我们想在 XAML 中使用一些常见的类型，比如 &lt;code>String&lt;/code>、&lt;code>Int32&lt;/code>、&lt;code>Boolean&lt;/code> 等，通常需要显式地引入 &lt;code>System&lt;/code> 命名空间：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="na">xmlns:sys=&lt;/span>&lt;span class="s">&amp;#34;clr-namespace:System;assembly=mscorlib&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;sys:String&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;MyString&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>Hello, World!&lt;span class="nt">&amp;lt;/sys:String&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;sys:Int32&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;MyInt&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>42&lt;span class="nt">&amp;lt;/sys:Int32&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;sys:Boolean&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;MyBool&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>True&lt;span class="nt">&amp;lt;/sys:Boolean&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">&lt;p>对于 .NET Framework 项目，我们必须引入 &lt;code>mscorlib&lt;/code> 程序集；而对于 .NET 5+ 项目，我们还将多一些选择，比如：&lt;/p>
&lt;ul>
&lt;li>&lt;code>System.Core&lt;/code>&lt;/li>
&lt;li>&lt;code>System.Runtime&lt;/code>&lt;/li>
&lt;li>&lt;code>netstandard&lt;/code>&lt;/li>
&lt;/ul>&lt;/div>
&lt;/div>
&lt;p>而在 Avalonia 中，我们可以直接使用 &lt;code>x&lt;/code> 命名空间来引用这些常见类型，无需额外的 &lt;code>xmlns&lt;/code> 声明：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;x:String&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;MyString&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>Hello, World!&lt;span class="nt">&amp;lt;/x:String&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;x:Int32&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;MyInt&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>42&lt;span class="nt">&amp;lt;/x:Int32&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;x:Boolean&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;MyBool&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>True&lt;span class="nt">&amp;lt;/x:Boolean&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="结语">
结语
&lt;a href="#%e7%bb%93%e8%af%ad" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>本文介绍了一些针对 WPF 开发者在使用 Avalonia 时的实用建议。通过了解和运用这些 Avalonia 的特性和最佳实践，WPF 开发者可以更好地适应 Avalonia 的开发环境，从而提升开发效率和应用性能。&lt;/p>
&lt;p>我们在使用 Avalonia 时，应该充分利用其提供的丰富功能和灵活语法，避免简单地将 WPF 的习惯直接套用到 Avalonia 上。希望本文的内容能够帮助大家更好地理解和使用 Avalonia，打造出高质量的跨平台应用程序。&lt;/p></description></item><item><title>在 C# 中使用 IComparer 实现自定义排序</title><link>https://blog.coldwind.top/posts/customize-order-with-icomparer/</link><pubDate>Thu, 09 Oct 2025 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/customize-order-with-icomparer/</guid><description>&lt;p>在 C# 中处理数据时，我们有时候会想给某种数据一种特殊的排列顺序。比如对于公司员工排序时，我们希望按照员工所属的部门进行排序，并且希望按照一定的优先级，比如“行政、财务、人力资源、市场、销售、运营、研发”这样的顺序。这种情况下，如果我们使用默认的排序（即字典序），就无法满足我们的需求。&lt;/p>
&lt;p>幸运的是，C# 提供了一个非常强大的接口 &lt;code>IComparer&lt;/code>，它允许我们自定义排序逻辑。通过实现这个接口，我们可以定义任何我们想要的排序规则。&lt;/p>
&lt;h2 id="icomparer-接口的定义">
IComparer 接口的定义
&lt;a href="#icomparer-%e6%8e%a5%e5%8f%a3%e7%9a%84%e5%ae%9a%e4%b9%89" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>&lt;code>IComparer&amp;lt;T&amp;gt;&lt;/code> 接口定义了一个方法 &lt;code>Compare&lt;/code>，它接受两个参数，并返回一个整数值。这个整数值的含义就是我们熟知的 &lt;code>CompareTo&lt;/code> 方法的返回值：&lt;/p>
&lt;ul>
&lt;li>-1：表示第一个参数小于第二个参数&lt;/li>
&lt;li>0：表示两个参数相等&lt;/li>
&lt;li>1：表示第一个参数大于第二个参数&lt;/li>
&lt;/ul>
&lt;p>所以，如果想要在使用各种常见排序方法（如 &lt;code>Array.Sort&lt;/code>、&lt;code>List&amp;lt;T&amp;gt;.Sort&lt;/code> 以及 LINQ 的 &lt;code>OrderBy&lt;/code> 等）时使用自定义的排序逻辑，我们需要有一个实现了这个接口的类，并且将一个实例传给这些方法。&lt;/p>
&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">这个接口还有一个非泛型版本 &lt;code>IComparer&lt;/code>，它的 &lt;code>Compare&lt;/code> 方法接受两个 &lt;code>object&lt;/code> 类型的参数。虽然非泛型版本在某些情况下可能会用到，但通常我们更倾向于使用泛型版本，因为它提供了类型安全性，并且也减少了很多我们在实现过程中可能遇到的类型转换问题。&lt;/div>
&lt;/div>
&lt;h2 id="一个简单的例子">
一个简单的例子
&lt;a href="#%e4%b8%80%e4%b8%aa%e7%ae%80%e5%8d%95%e7%9a%84%e4%be%8b%e5%ad%90" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>以我们前面提到的员工排序为例，假设我们有一个 &lt;code>Employee&lt;/code> 类：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Employee&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Department&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我们可以创建一个 &lt;code>EmployeeComparer&lt;/code> 类来实现 &lt;code>IComparer&amp;lt;Employee&amp;gt;&lt;/code> 接口：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">EmployeeComparer&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">IComparer&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Employee&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">DepartmentOrder&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;行政&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;财务&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;人力资源&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;市场&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;销售&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;运营&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;研发&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Compare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Employee&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Employee&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="p">||&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ArgumentNullException&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">xIndex&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">DepartmentOrder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IndexOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Department&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">yIndex&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">DepartmentOrder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IndexOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Department&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">xIndex&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">xIndex&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">MaxValue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">yIndex&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">yIndex&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">MaxValue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">deptComparison&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">xIndex&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CompareTo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">yIndex&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">deptComparison&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">deptComparison&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Compare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">StringComparison&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Ordinal&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>非泛型版本与上面类似，只是参数类型变成了 &lt;code>object&lt;/code>，并且需要进行类型转换。这里不再赘述。&lt;/p>
&lt;p>接下来，我们可以使用这个比较器来排序一个员工列表：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Employee&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">employees&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">GetEmployeesAsync&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">comparer&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">EmployeeComparer&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">employees&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Sort&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">comparer&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 或者使用 LINQ&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">sortedEmployees&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">employees&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">OrderBy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">comparer&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">ThenBy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">ToList&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="一个更通用的例子">
一个更通用的例子
&lt;a href="#%e4%b8%80%e4%b8%aa%e6%9b%b4%e9%80%9a%e7%94%a8%e7%9a%84%e4%be%8b%e5%ad%90" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>如果在我们的项目中，时常会遇到这样的情形，也就是我们希望手动指定一种数据类型的排序方式。那么上面的方式可能就不够灵活了，因为我们需要为每一种数据类型都创建一个比较器类。&lt;/p>
&lt;p>此时，我们可以写一个更加通用的比较器类 &lt;code>CustomComparer&amp;lt;T&amp;gt;&lt;/code>，它接受一个排序规则列表：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">sealed&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">CustomComparer&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">IComparer&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">Dictionary&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_customOrder&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">CustomComparer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">params&lt;/span> &lt;span class="n">T&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">customOrder&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_customOrder&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">customOrder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Index&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">ToDictionary&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Item&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Index&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Compare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">T&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">y&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">y&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">_customOrder&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">j&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">_customOrder&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CompareTo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">j&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>使用这个通用比较器，我们可以很方便地为任何类型指定排序规则：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">departments&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s">&amp;#34;行政&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;财务&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;人力资源&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;市场&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;销售&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;运营&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;研发&amp;#34;&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">departmentComparer&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">CustomComparer&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="n">departments&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">sortedEmployees&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">employees&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">OrderBy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Department&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">departmentComparer&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">ThenBy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">ToList&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">和前面的例子略有不同的是，这里我们只针对 &lt;code>string&lt;/code> 这个类型定制了比较器。因此在使用时，我们需要指定 &lt;code>OrderBy&lt;/code> 的第一个参数为 &lt;code>e =&amp;gt; e.Department&lt;/code>，而不是直接传入 &lt;code>e&lt;/code>。而在前面的例子中，我们直接针对的比较对象就是 &lt;code>Employee&lt;/code> 类型，然后我们在 &lt;code>Compare&lt;/code> 方法中处理了 &lt;code>Department&lt;/code> 属性。&lt;/div>
&lt;/div>
&lt;p>通过实现 &lt;code>IComparer&lt;/code> 接口，我们可以轻松地为任何数据类型定制排序逻辑。这不仅提升了代码的灵活性，也使得我们能够更好地控制数据的展示顺序，从而满足各种业务需求。&lt;/p>
&lt;h2 id="其他方式">
其他方式
&lt;a href="#%e5%85%b6%e4%bb%96%e6%96%b9%e5%bc%8f" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>除了使用 &lt;code>IComparer&lt;/code> 接口，我们还有一些别的方法。比如 &lt;code>Sort&lt;/code>、&lt;code>OrderBy&lt;/code> 等方法允许我们传入一个 &lt;code>Comparison&amp;lt;T&amp;gt;&lt;/code> 委托。它的声明如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">delegate&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Comparison&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="k">in&lt;/span> &lt;span class="n">T&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="n">T&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">T&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>所以我们可以使用 lambda 表达式来定义排序逻辑：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Employee&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">employees&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">GetEmployees&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Comparison&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Employee&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">comparison&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">departmentOrder&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s">&amp;#34;行政&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;财务&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;人力资源&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;市场&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;销售&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;运营&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;研发&amp;#34;&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">xIndex&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">departmentOrder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IndexOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Department&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">yIndex&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">departmentOrder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IndexOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Department&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">xIndex&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">xIndex&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">MaxValue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">yIndex&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">yIndex&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">MaxValue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">deptComparison&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">xIndex&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CompareTo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">yIndex&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">deptComparison&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">deptComparison&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Compare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">StringComparison&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Ordinal&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">employees&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Sort&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">comparison&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>另外，我们还可以直接修改类型本身，来实现 &lt;code>IComparable&lt;/code> 接口，从而定义默认的排序逻辑。这种方法我们就不再做具体介绍了，因为它存在显著的局限性：每个类型只能有一种默认排序方式。如果我们将 &lt;code>Employee&lt;/code> 类型的比较方式硬性定义为了“先部门，后姓名”的方式，那么此后如果我们想在使用 LINQ 时采用别的排序方式，就会变得非常麻烦。这种方式只适合非常简单的数据类型及场景，比如我们定义了一种包装类，它实际排序依靠的是内部的某个数值属性。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>通过实现 &lt;code>IComparer&lt;/code> 接口，我们可以为任何数据类型定制排序逻辑，从而满足各种复杂的业务需求。无论是为特定类型创建专用的比较器，还是使用通用的比较器类，我们都能够灵活地控制数据的排序方式。此外，使用 &lt;code>Comparison&amp;lt;T&amp;gt;&lt;/code> 委托也是一种简便的方法，适用于简单的排序需求。总之，掌握这些技术，可以让我们在处理数据时更加得心应手。&lt;/p></description></item><item><title>盘点 LINQ 在最近几个 .NET 版本中新增的功能和特性</title><link>https://blog.coldwind.top/posts/linq-new-features-added-in-recent-dotnet/</link><pubDate>Thu, 25 Sep 2025 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/linq-new-features-added-in-recent-dotnet/</guid><description>&lt;p>LINQ 表达式相信每一位 C# 开发者都不陌生，LINQ 作为 C# 语言的核心功能之一，极大地简化了数据查询和操作的过程。随着 .NET 平台的不断发展，LINQ 也在不断地引入新的特性和改进，以提升开发者的生产力和代码的可读性。本文将介绍最近几个版本的 .NET 中新增的 LINQ 特性。&lt;/p>
&lt;h2 id="net-6">
.NET 6
&lt;a href="#net-6" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>.NET 5 作为首次合并了 .NET Framework 和 .NET Core 的版本，标志着 .NET 生态系统的统一。这一版本并没有引入什么新的 LINQ 特性，但是在随后的第一个 LTS 版本 .NET 6 中，微软引入了很多新功能。&lt;/p>
&lt;h3 id="1-chunk-方法">
1. Chunk 方法
&lt;a href="#1-chunk-%e6%96%b9%e6%b3%95" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>&lt;code>Chunk&lt;/code> 方法允许开发者将一个序列分割成多个固定大小的块。这在处理大数据集时非常有用，可以帮助减少内存占用和提高性能。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">numbers&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Enumerable&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">10&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">chunks&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">numbers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Chunk&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">3&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">chunk&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">chunks&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;, &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">chunk&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 输出:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 1, 2, 3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 4, 5, 6&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 7, 8, 9&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 10&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>如果最后一块的元素数量不足指定大小，它将包含剩余的所有元素。&lt;/p>
&lt;h3 id="2-minby--maxby">
2. MinBy &amp;amp; MaxBy
&lt;a href="#2-minby--maxby" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>在以前我们有 &lt;code>Min&lt;/code> 和 &lt;code>Max&lt;/code> 方法，用来获取序列中的最小值和最大值。这对于最传统的值类型，尤其是数字类型来说是非常易用且易懂的：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">numbers&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">5&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">min&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">numbers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Min&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">max&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">numbers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Max&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 5&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但如果我们面对的是一个较为复杂的对象，比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Person&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Age&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>现在我们想获取年龄最大的人，使用传统的方式就会比较繁琐且性能低下了：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">people&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Person&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="n">Person&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Alice&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Age&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">30&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="n">Person&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Bob&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Age&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">25&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="n">Person&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Charlie&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Age&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">35&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 方法一&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">oldestPerson&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">people&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OrderByDescending&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Age&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">First&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 方法二&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">oldestAge&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">people&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Max&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Age&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">oldestPerson&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">people&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">First&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Age&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="n">oldestAge&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>而在 .NET 6 中，我们可以直接使用 &lt;code>MaxBy&lt;/code> 和 &lt;code>MinBy&lt;/code> 方法来简化这个过程：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">oldestPerson&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">people&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">MaxBy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Age&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">youngestPerson&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">people&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">MinBy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Age&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="3-distinctby-等">
3. DistinctBy 等
&lt;a href="#3-distinctby-%e7%ad%89" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>与上面的 &lt;code>MinBy&lt;/code> 和 &lt;code>MaxBy&lt;/code> 类似，&lt;code>DistinctBy&lt;/code> 等方法也是允许我们基于某个属性来进行去重、交集和差集操作，并最终返回原始对象。比如下面的例子中，我们可以得到所有名字不重复的人：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">people&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Person&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="n">Person&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Alice&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Age&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">30&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="n">Person&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Bob&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Age&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">25&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="n">Person&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Charlie&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Age&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">35&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="n">Person&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Alice&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Age&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">28&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">distinctByName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">people&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">DistinctBy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Name&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>IntersectBy&lt;/code> 和 &lt;code>ExceptBy&lt;/code> 也是类似的用法。它们允许我们基于某个属性来进行交集和差集操作。具体的代码这里就不展示了，大家在用到的时候相信很快就能上手。&lt;/p>
&lt;h3 id="4-firstordefault-等方法的重载">
4. FirstOrDefault 等方法的重载
&lt;a href="#4-firstordefault-%e7%ad%89%e6%96%b9%e6%b3%95%e7%9a%84%e9%87%8d%e8%bd%bd" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>在这个版本中，&lt;code>FirstOrDefault&lt;/code>、&lt;code>LastOrDefault&lt;/code>、&lt;code>SingleOrDefault&lt;/code> 这些方法允许传入一个自定义的默认值，而不是返回类型的默认值（例如 &lt;code>null&lt;/code> 或 &lt;code>0&lt;/code>）。这在某些情况下可以减少代码量，提高可读性。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">num&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">numbers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FirstOrDefault&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="p">&amp;gt;&lt;/span> &lt;span class="m">10&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 如果没有找到符合条件的元素，返回 -1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">student&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">students&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SingleOrDefault&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Id&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Student&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">Id&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Unknown&amp;#34;&lt;/span> &lt;span class="p">});&lt;/span> &lt;span class="c1">// 如果没有找到符合条件的元素，返回一个新的 Student 对象，而不是 null&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这对于引用类型来说，或许可以借助 &lt;code>??&lt;/code> 操作符来实现类似的功能；但对于值类型来说，这个重载就显得非常好用了。&lt;/p>
&lt;h3 id="5-take">
5. Take
&lt;a href="#5-take" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>C# 8 引入了索引和范围的概念，这使得我们可以更方便地从集合中获取子集。比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">numbers&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Enumerable&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">10&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">ToArray&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">firstThree&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">numbers&lt;/span>&lt;span class="p">[..&lt;/span>&lt;span class="m">3&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// 获取前3个元素&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">lastThree&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">numbers&lt;/span>&lt;span class="p">[^&lt;/span>&lt;span class="m">3.&lt;/span>&lt;span class="p">.];&lt;/span> &lt;span class="c1">// 获取后3个元素&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">middleThree&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">numbers&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="m">3.&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="m">6&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// 获取第4到第6个元素&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>现在，&lt;code>Take&lt;/code> 方法也支持传入一个范围。这样我们就不需要再搭配使用 &lt;code>Skip&lt;/code> 等方法了：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">numbers&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Enumerable&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">10&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 以前的做法&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">middleThree&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">numbers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Skip&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">3&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">Take&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">3&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 现在可以直接使用范围&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">middleThree&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">numbers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Take&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">3.&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="m">6&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="6-zip">
6. Zip
&lt;a href="#6-zip" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>对于 &lt;code>Zip&lt;/code> 方法，现在多了一个重载，允许我们组合三个序列。或许在特定情况下，这一功能会派上用场。但奇怪的是，微软并没有提供更多的重载来支持更多的序列。&lt;/p>
&lt;p>如果我们有组合更多序列的需求，可以考虑多次使用 &lt;code>Zip&lt;/code> 方法来实现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">numbers1&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">3&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">numbers2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="m">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">6&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">numbers3&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="m">7&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">8&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">9&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">zipped&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">numbers1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Zip&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">numbers2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n2&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Zip&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">numbers3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">pair&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n3&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">pair&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">n1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">pair&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">n2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n3&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="net-7">
.NET 7
&lt;a href="#net-7" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>这个版本虽然在方法上仅新增了两个，但 LINQ 在性能上得到了显著提升。微软对 LINQ 的实现进行了优化，减少了内存分配和提高了执行速度。&lt;/p>
&lt;h3 id="order--orderdescending">
Order &amp;amp; OrderDescending
&lt;a href="#order--orderdescending" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>在 .NET 7 中，微软引入了 &lt;code>Order&lt;/code> 和 &lt;code>OrderDescending&lt;/code> 方法，这两个方法允许我们对序列进行排序，而不需要指定排序的键。它们会根据元素默认的比较器进行排序。&lt;/p>
&lt;p>有了这个新方法，当我们不需要指定排序键时，代码会更加简洁，而且因为减少了委托的使用，也会略微减小一些性能开销：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">numbers&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="m">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">9&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">sortedNumbers&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">numbers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OrderBy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">sortedNumbers&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">numbers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Order&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>不过这里仍然有必要强调一下，对于传统的集合类型，比如数组和 &lt;code>List&amp;lt;T&amp;gt;&lt;/code>，我们如果有原地（in-place）排序的需求，还是应该使用它们自带的 &lt;code>Sort&lt;/code> 方法，因为它们会直接修改原始集合。这种时候如果使用 LINQ 的 &lt;code>Order().ToArray()&lt;/code> 等方法，反而会带来不必要的内存分配和性能开销：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">arr&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="m">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">9&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">list&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="m">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">9&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Array&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Sort&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">arr&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 原地排序&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">list&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Sort&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 原地排序&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="net-8">
.NET 8
&lt;a href="#net-8" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>这个版本并没有引入新的 LINQ 方法，但值得一提的是，&lt;code>Random&lt;/code> 新引入了 &lt;code>Shuffle&lt;/code> 方法，也就是洗牌算法。这个方法可以随机打乱一个序列的顺序：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">numbers&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Enumerable&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">10&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">shuffledNumbers&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Random&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Shared&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Shuffle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">numbers&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="net-9">
.NET 9
&lt;a href="#net-9" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;h3 id="index">
Index
&lt;a href="#index" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>在 .NET 9 中，微软引入了 &lt;code>Index&lt;/code> 方法。这个方法并不是类似 &lt;code>IndexOf&lt;/code>，而是可以将一个序列包装为一些包含了 &lt;code>Index&lt;/code> 和 &lt;code>Item&lt;/code> 的元组（&lt;code>ValueTuple&lt;/code>），方便我们在遍历时获取元素的索引。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">IEnumerable&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">numbers&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="m">10&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">20&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">30&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">40&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">50&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">index&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">item&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">numbers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Index&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">$&amp;#34;Index: {index}, Item: {item}&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>其实在以前，我们借助 &lt;code>Select&lt;/code> 方法也可以实现类似的功能。&lt;code>Select&lt;/code> 方法有一些重载，允许我们在选择元素的同时获取它们的索引：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">pair&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">numbers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Select&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">index&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">index&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">item&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">$&amp;#34;Index: {pair.index}, Item: {pair.item}&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>不过 &lt;code>Index&lt;/code> 方法的语义会更加明确一些，也更加易用了。&lt;/p>
&lt;h3 id="countby">
CountBy
&lt;a href="#countby" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>&lt;code>CountBy&lt;/code> 方法允许我们根据某个键对序列进行分组，并计算每个组的元素数量。它返回一个包含键和值的元组序列。&lt;/p>
&lt;p>比如我们有一个产品列表：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Product&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Category&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我们可以使用 &lt;code>CountBy&lt;/code> 方法来统计每个类别的产品数量：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">products&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Product&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="n">Product&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Apple&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Category&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Fruit&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="n">Product&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Banana&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Category&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Fruit&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="n">Product&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Carrot&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Category&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Vegetable&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="n">Product&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Broccoli&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Category&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Vegetable&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="n">Product&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Chicken&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Category&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Meat&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">categoryCounts&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">products&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CountBy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Category&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 结果:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// [(&amp;#34;Fruit&amp;#34;, 2), (&amp;#34;Vegetable&amp;#34;, 2), (&amp;#34;Meat&amp;#34;, 1)]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在以前，我们可以借助 &lt;code>GroupBy&lt;/code> 方法来实现类似的功能：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">categoryCounts&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">products&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">GroupBy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Category&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Select&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">g&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">Category&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">g&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Count&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">g&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Count&lt;/span>&lt;span class="p">()));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="aggregateby">
AggregateBy
&lt;a href="#aggregateby" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>&lt;code>AggregateBy&lt;/code> 方法允许我们根据某个键对序列进行分组，并对每个组应用一个聚合函数。它返回一个包含键和值的元组序列。&lt;/p>
&lt;p>首先我们简单回顾一下 &lt;code>Aggregate&lt;/code> 方法。这个方法允许我们对序列中的元素进行累积操作，比如计算总和、乘积等：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">numbers&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">5&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">sum&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">numbers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Aggregate&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="n">acc&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">acc&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 15&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">product&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">numbers&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Aggregate&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="n">acc&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">acc&lt;/span> &lt;span class="p">*&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 120&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>现在，&lt;code>AggregateBy&lt;/code> 方法允许我们先根据某个键对序列进行分组，然后对每个组应用一个聚合函数。比如我们有一些订单，我们想计算每个客户的订单总金额：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Order&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Customer&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">decimal&lt;/span> &lt;span class="n">Amount&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">results&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">orders&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AggregateBy&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">order&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">order&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Customer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 分组键&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">acc&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">order&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">acc&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="n">order&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Amount&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 聚合函数&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">0&lt;/span>&lt;span class="n">m&lt;/span> &lt;span class="c1">// 初始值&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">这个例子看似可以用 &lt;code>GroupBy&lt;/code> 和 &lt;code>Select&lt;/code> 来实现，但实际上 &lt;code>AggregateBy&lt;/code> 的性能会更好一些，因为它避免了中间集合的创建。&lt;code>GroupBy&lt;/code> 会创建一个中间的分组集合，而 &lt;code>AggregateBy&lt;/code> 则直接在遍历时进行聚合操作。&lt;/div>
&lt;/div>
&lt;h2 id="关于-by-的思考">
关于 By 的思考
&lt;a href="#%e5%85%b3%e4%ba%8e-by-%e7%9a%84%e6%80%9d%e8%80%83" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在 LINQ 中，名称带有 &lt;code>By&lt;/code> 的方法有很多。这里的 &lt;code>By&lt;/code> 虽然都表示要基于某个方式（比如属性、键等）来进行操作，但它们为方法带来的语义并不完全相同。一般来说分为两种情形：&lt;/p>
&lt;ul>
&lt;li>基于某个键进行操作，并在最后返回对象本身（而不是这个键）
&lt;ul>
&lt;li>&lt;code>MinBy&lt;/code>、&lt;code>MaxBy&lt;/code>、&lt;code>OrderBy&lt;/code>、&lt;code>DistinctBy&lt;/code>、&lt;code>IntersectBy&lt;/code>、&lt;code>ExceptBy&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>基于某个键进行分组（再进行后续操作）
&lt;ul>
&lt;li>&lt;code>GroupBy&lt;/code>、&lt;code>CountBy&lt;/code>、&lt;code>AggregateBy&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>因为两种不同的语义，某些方法其实是有可能引起误会的。比如 &lt;code>DintinctBy&lt;/code>，到底应该是以某个键来去重，还是应该以某个键分组后再在每组中进行去重呢？&lt;/p>
&lt;p>细心观察会发现，LINQ 并没有提供诸如 &lt;code>SumBy&lt;/code>、&lt;code>AverageBy&lt;/code> 等方法。因为这些方法首先没有什么必要，其次也会引起歧义。比如 &lt;code>SumBy&lt;/code>，它是应该先分组再求和，还是直接对某个键求和呢？如果是前者，那它的语义就和 &lt;code>AggregateBy&lt;/code> 很接近了；如果是后者，那它其实和传了一个 &lt;code>selector&lt;/code> 的 &lt;code>Sum&lt;/code> 一样。&lt;/p>
&lt;p>那么 &lt;code>CountBy&lt;/code> 又该怎么去理解呢？如果将 &lt;code>Count&lt;/code> 理解为一种聚合操作，那么它其实和 &lt;code>AggregateBy&lt;/code> 的语义是类似的。那么是不是说，对于一个聚合操作，它的 &lt;code>By&lt;/code> 意思就是分组了呢？其实也未必，因为 &lt;code>Max&lt;/code> 同样也可以看作是一种聚合操作，或者最起码我们确实可以用一个 &lt;code>Aggregate&lt;/code> 来实现不是吗？所以这些方法的名称可能有点绕，需要大家在使用时多加注意。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>随着 .NET 平台的不断发展，LINQ 也在不断地引入新的特性和改进。这些新特性不仅提升了开发者的生产力，也使代码更加简洁和易读。希望本文能帮助大家更好地理解和使用这些新特性，提升开发效率。同时也鼓励大家，在有条件的情况下，尽量使用最新版本的 .NET，以便享受到这些改进和优化带来的好处。&lt;/p></description></item><item><title>C# 获取文件大小的几种方式及它们的性能比较</title><link>https://blog.coldwind.top/posts/how-to-get-file-size/</link><pubDate>Sat, 06 Sep 2025 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/how-to-get-file-size/</guid><description>&lt;img src="https://s2.loli.net/2025/09/06/WBHdw1K6rio2hxP.jpg" alt="Featured image of post C# 获取文件大小的几种方式及它们的性能比较" />&lt;p>我们在操作文件时，经常需要获取文件的大小。相信大家都知道 &lt;code>FileInfo&lt;/code> 类有一个 &lt;code>Length&lt;/code> 属性可以获取文件大小，但实际上我们还有一些别的方式，并且其他方式可能比 &lt;code>FileInfo&lt;/code> 有更好的性能。这篇文章我们就来盘点一下 C# 中获取文件大小的几种方式，并简单比较一下它们的性能。&lt;/p>
&lt;h2 id="使用-fileinfolength">
使用 FileInfo.Length
&lt;a href="#%e4%bd%bf%e7%94%a8-fileinfolength" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>这个是最常见的方式，&lt;code>FileInfo&lt;/code> 类提供了一个 &lt;code>Length&lt;/code> 属性，可以直接获取文件的大小，单位是字节（bytes）。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.IO&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">FileInfo&lt;/span> &lt;span class="n">fileInfo&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">FileInfo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">long&lt;/span> &lt;span class="n">fileSize&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">fileInfo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这种方式的优点是代码简洁易懂，并且几乎适用于所有场景，包括跨平台开发。它唯一美中不足的地方在于，&lt;code>FileInfo&lt;/code> 在使用前需要实例化，这会带来一点 GC 开销。&lt;/p>
&lt;p>另外，如果觉得返回的 &lt;code>long&lt;/code> 类型不够直观，我们也可以将其转换为更常见的单位，比如 KB、MB、GB 等。对于这个需求，除了自己写转换代码，我们还可以使用 &lt;a class="link" href="https://github.com/Humanizr/Humanizer" target="_blank" rel="noopener"
>Humanizer&lt;/a> 这个库，它提供了非常方便的文件大小格式化功能。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">Humanizer&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">FileInfo&lt;/span> &lt;span class="n">fileInfo&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">FileInfo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">string&lt;/span> &lt;span class="n">humanizedSize&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">fileInfo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Bytes&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">Humanize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;0.00&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// e.g. &amp;#34;1.23 MB&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="使用-randomaccess">
使用 RandomAccess
&lt;a href="#%e4%bd%bf%e7%94%a8-randomaccess" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>&lt;code>RandomAccess&lt;/code> 是一个在。NET 6 中引入的静态类，旨在提供高性能、线程安全的文件随机访问 I/O 操作。它提供的 &lt;code>GetLength&lt;/code> 方法可以直接获取文件的大小。但稍微有些可惜的是，虽然它的 &lt;code>GetLength&lt;/code> 方法是静态且不需要创建对象的，但它需要传入一个文件句柄（file handle），而后者是一个 &lt;code>SafeFileHandle&lt;/code> 对象，这就不可避免地引入了 &lt;code>FileStream&lt;/code> 对象的创建开销。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.IO&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">handle&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">File&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OpenHandle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">FileMode&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Open&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">FileAccess&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Read&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">FileShare&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Read&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">FileOptions&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">RandomAccess&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">long&lt;/span> &lt;span class="n">fileSize&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">RandomAccess&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetLength&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">handle&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>那么实际上这个方式的效果怎么样呢？在后面的跑分环节会揭晓答案。&lt;/p>
&lt;h2 id="使用-pinvoke-调用-windows-api">
使用 P/Invoke 调用 Windows API
&lt;a href="#%e4%bd%bf%e7%94%a8-pinvoke-%e8%b0%83%e7%94%a8-windows-api" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>当程序运行的平台是 Windows 时，我们还可以通过 P/Invoke 调用 Windows API 来获取文件大小。这个方式的好处是它不需要创建任何托管对象，因此理论上它的性能应该是最好的。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.IO&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Runtime.InteropServices&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Runtime.InteropServices.ComTypes&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[DllImport(&amp;#34;kernel32.dll&amp;#34;, CharSet = CharSet.Auto, SetLastError = true)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="kd">extern&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">GetFileAttributesEx&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">string&lt;/span> &lt;span class="n">lpFileName&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">fInfoLevelId&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">out&lt;/span> &lt;span class="n">WIN32_FILE_ATTRIBUTE_DATA&lt;/span> &lt;span class="n">lpFileInformation&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">GetFileSizeWin32&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(!&lt;/span>&lt;span class="n">GetFileAttributesEx&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">out&lt;/span> &lt;span class="kt">var&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ComponentModel&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Win32Exception&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="kt">long&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">nFileSizeHigh&lt;/span> &lt;span class="p">&amp;lt;&amp;lt;&lt;/span> &lt;span class="m">32&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">nFileSizeLow&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[StructLayout(LayoutKind.Sequential)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">struct&lt;/span> &lt;span class="nc">WIN32_FILE_ATTRIBUTE_DATA&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">uint&lt;/span> &lt;span class="n">dwFileAttributes&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">FILETIME&lt;/span> &lt;span class="n">ftCreationTime&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ftLastAccessTime&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ftLastWriteTime&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">uint&lt;/span> &lt;span class="n">nFileSizeHigh&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">nFileSizeLow&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后我们就可以使用上面的 &lt;code>GetFileSizeWin32&lt;/code> 方法来获取文件大小了。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">如果是在 Linux 或 macOS 上运行的程序，那么虽然可以采用诸如引入 &lt;code>Mono.Posix&lt;/code> 之类的库来调用系统 API，但这样做的复杂度和维护成本会比较高，所以推荐直接使用 &lt;code>FileInfo&lt;/code>。&lt;/div>
&lt;/div>
&lt;h2 id="使用-visualbasicfileio">
使用 VisualBasic.FileIO
&lt;a href="#%e4%bd%bf%e7%94%a8-visualbasicfileio" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>最后这种方式可能有凑数的嫌疑，但是我们确实可以借助 &lt;code>Microsoft.VisualBasic&lt;/code> 命名空间下的 &lt;code>FileSystem&lt;/code> 类来获取文件大小。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">Microsoft.VisualBasic.FileIO&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">long&lt;/span> &lt;span class="n">fileSize&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">FileSystem&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FileLen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">Length&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="性能比较">
性能比较
&lt;a href="#%e6%80%a7%e8%83%bd%e6%af%94%e8%be%83" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>以上就是四种可行的获取文件大小的方式。接下来我们来比较一下它们的性能。结果如下：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Method&lt;/th>
&lt;th style="text-align: right">Mean&lt;/th>
&lt;th style="text-align: right">Error&lt;/th>
&lt;th style="text-align: right">StdDev&lt;/th>
&lt;th style="text-align: right">Allocated&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>UseFileInfo&lt;/td>
&lt;td style="text-align: right">7.888 μs&lt;/td>
&lt;td style="text-align: right">0.0522 μs&lt;/td>
&lt;td style="text-align: right">0.0462 μs&lt;/td>
&lt;td style="text-align: right">96 B&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>UseWin32Api&lt;/td>
&lt;td style="text-align: right">7.732 μs&lt;/td>
&lt;td style="text-align: right">0.0740 μs&lt;/td>
&lt;td style="text-align: right">0.0692 μs&lt;/td>
&lt;td style="text-align: right">-&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>UseFileSystem&lt;/td>
&lt;td style="text-align: right">15.779 μs&lt;/td>
&lt;td style="text-align: right">0.2104 μs&lt;/td>
&lt;td style="text-align: right">0.1968 μs&lt;/td>
&lt;td style="text-align: right">96 B&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>UseRandomAccess&lt;/td>
&lt;td style="text-align: right">10.627 μs&lt;/td>
&lt;td style="text-align: right">0.1137 μs&lt;/td>
&lt;td style="text-align: right">0.1063 μs&lt;/td>
&lt;td style="text-align: right">72 B&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>从结果中我们不难得出以下几个结论：&lt;/p>
&lt;ol>
&lt;li>使用 Windows API 的方式性能最好，并且没有任何托管内存分配。但它只能在 Windows 平台使用。&lt;/li>
&lt;li>使用 &lt;code>FileInfo&lt;/code> 的方式性能也不错，适用于绝大多数场景。&lt;/li>
&lt;li>使用 &lt;code>RandomAccess&lt;/code> 的方式性能一般，虽然它不需要创建 &lt;code>FileInfo&lt;/code> 对象，但它需要创建 &lt;code>FileStream&lt;/code> 对象来获取文件句柄，这带来了无法避免的开销（虽然比 &lt;code>FileInfo&lt;/code> 少了一点）。&lt;/li>
&lt;li>使用 &lt;code>VisualBasic.FileIO&lt;/code> 的方式性能最差，并且还会有内存分配，基本上没有任何优势。&lt;/li>
&lt;/ol>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>综上所述，几乎在任何情况下，我们都可以优先考虑使用 &lt;code>FileInfo.Length&lt;/code> 来获取文件大小。只有在对于性能有极致要求，并且程序运行的平台确定是 Windows 的情况下，才考虑使用 P/Invoke 调用 Windows API 的方式。&lt;/p></description></item><item><title>原生 WPF 框架中体现出的设计模式</title><link>https://blog.coldwind.top/posts/built-in-design-patterns-in-wpf/</link><pubDate>Mon, 11 Aug 2025 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/built-in-design-patterns-in-wpf/</guid><description>&lt;p>我们在做软件开发时，经常会使用设计模式，比如单例模式、工厂模式、观察者模式等。这些设计模式帮助我们更好地组织代码，提高代码的可维护性和可扩展性。&lt;/p>
&lt;p>但是这次我们换一个角度，来看一看框架本身有没有体现出这些设计模式。希望这篇文章可以给大家一个不一样的视角。&lt;/p>
&lt;h2 id="观察者模式">
观察者模式
&lt;a href="#%e8%a7%82%e5%af%9f%e8%80%85%e6%a8%a1%e5%bc%8f" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>对于观察者模式，相信大多数人可能首先会想到的是在 MVVM 模式下，ViewModel 作为数据的提供者，而 View 作为数据的消费者。当 ViewModel 中的数据发生变化时，它会通知所有绑定的 View 进行更新（具体做法为实现 &lt;code>INotifyPropertyChanged&lt;/code> 接口，并触发相应的事件）。这种模式使得 View 和 ViewModel 之间的交互变得简单而高效。&lt;/p>
&lt;p>但实际上，原生 WPF 就已经充分体现出了观察者模式。对于大多数控件，本身它就提供了两种实现了观察者模式，或者说可以被观察的特性：&lt;/p>
&lt;ol>
&lt;li>事件：控件的事件机制允许开发者注册事件处理程序，从而在特定事件发生时接收通知。例如，当用户点击按钮时，按钮会触发 Click 事件；当用户修改文本框的内容时，文本框会触发 TextChanged 事件。&lt;/li>
&lt;li>依赖属性：控件的依赖属性发生变化时，如果有绑定的目标，它会自动通知这些目标进行更新。这种机制使得控件的状态变化能够被及时“观察”到，从而实现了观察者模式的效果。&lt;/li>
&lt;/ol>
&lt;p>所以，在 WPF 开发中，观察者模式并不仅仅体现在 MVVM 模式上，它在控件的事件和依赖属性中也得到了充分的体现。&lt;/p>
&lt;h2 id="桥接模式">
桥接模式
&lt;a href="#%e6%a1%a5%e6%8e%a5%e6%a8%a1%e5%bc%8f" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>我们简单回顾一下桥接模式大概是怎么一回事：&lt;/p>
&lt;p>如果我们的某一个类存在多个维度的变化（比如不同的外观和不同的行为），比如图形类可能有不同的形状（Shape）以及颜色（Color），那么假如我们采用传统的 OOP 的思想，就不可避免地会引入大量的子类，比如 &lt;code>RedRectangle&lt;/code>、&lt;code>BlueCircle&lt;/code> 等，这显然是不理想的。&lt;/p>
&lt;p>此时，我们就可以使用桥接模式将这两个维度分离开来。这样一来，我们就可以独立地对这两个维度进行扩展，而不必相互影响。&lt;/p>
&lt;p>这时候我们回来看 WPF，比如 &lt;code>Control&lt;/code> 类就包含了多种不同的实现，包括 &lt;code>Button&lt;/code>、&lt;code>Label&lt;/code>、&lt;code>TextBox&lt;/code> 等；同时，它们又都包含 &lt;code>Background&lt;/code>、&lt;code>Foreground&lt;/code> 等属性，用于控制它们实际的外观。这是否就和我们上面的例子不谋而合了？&lt;/p>
&lt;p>所以，WPF 的 &lt;code>Control&lt;/code> 类就是一个典型的桥接模式的实现。它将控件的外观（如样式、模板）和行为（如事件、命令）分离开来，使得我们可以独立地对这两个维度进行扩展。当然，体现桥接模式的不仅仅是 &lt;code>Control&lt;/code> 类，其他很多控件也都遵循了这一模式。&lt;/p>
&lt;h2 id="装饰器模式">
装饰器模式
&lt;a href="#%e8%a3%85%e9%a5%b0%e5%99%a8%e6%a8%a1%e5%bc%8f" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>装饰器模式（Decorator Pattern）是一种结构性设计模式，它允许在不改变对象自身的情况下，动态地给对象添加一些额外的职责。装饰器模式通常用于遵循开闭原则（对扩展开放，对修改关闭）。&lt;/p>
&lt;p>在 WPF 中，装饰器模式的一个典型应用就是附加属性和行为了。通过附加属性，我们可以在不修改原有控件的情况下，为其添加新的功能。例如，我们可以为一个 &lt;code>TextBox&lt;/code> 控件添加一个附加属性，用于控制其是否显示占位符文本。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="na">local:TextBoxHelper.Placeholder=&lt;/span>&lt;span class="s">&amp;#34;请输入内容&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在这个例子中，&lt;code>TextBoxHelper&lt;/code> 就是一个提供了附加属性的类，它可以为任意 &lt;code>TextBox&lt;/code> 控件添加占位符文本（&lt;code>Placeholder&lt;/code>）属性。然后我们就可以在 &lt;code>TextBox&lt;/code> 的 &lt;code>Style&lt;/code> 或 &lt;code>Template&lt;/code> 中响应这个附加属性，从而真的为文本框添加占位符。&lt;/p>
&lt;p>行为也类似，而且行为本质上也是附加属性，或者说就是依靠附加属性来实现的。所以这里不再赘述。&lt;/p>
&lt;p>所以我们可以说，附加属性利用了装饰器模式。不仅如此，其实它还体现出了其他一些设计模式，比如我们开头提到的观察者模式，此外还有享元模式（附加属性与依赖属性为同一类型的控件提供了相同的属性元数据）、中介者模式（比如一些与布局相关的附加属性，如 &lt;code>DockPanel.Dock&lt;/code>、&lt;code>Grid.Row&lt;/code> 等）等等。&lt;/p>
&lt;h2 id="适配器模式">
适配器模式
&lt;a href="#%e9%80%82%e9%85%8d%e5%99%a8%e6%a8%a1%e5%bc%8f" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在 WPF 的绑定中，我们常常会利用到值转换器（&lt;code>ValueConverter&lt;/code>），它就是适配器模式的典型体现。&lt;/p>
&lt;p>这里我们看一个最典型的例子：我们要将一个布尔属性绑定到控件的 &lt;code>Visibility&lt;/code> 属性上。为了能够实现源类型（&lt;code>bool&lt;/code>）到目标类型（&lt;code>Visibility&lt;/code>）的转换，最常见的方式就是借助 &lt;code>BooleanToVisibilityConverter&lt;/code> 了：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;BooleanToVisibilityConverter&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;BooleanToVisibilityConverter&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;Hello, World!&amp;#34;&lt;/span> &lt;span class="na">Visibility=&lt;/span>&lt;span class="s">&amp;#34;{Binding IsTextVisible, Converter={StaticResource BooleanToVisibilityConverter}}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>另外，值转换器不仅体现出了适配器模式，还体现出了一定程度的策略模式。它们把“如何将一个值转换成另一个值”的算法抽象成一个接口（&lt;code>IValueConverter&lt;/code>），并通过不同的实现类来提供具体的转换逻辑，这正是策略模式的核心思想——把一系列可互换的算法封装为独立的策略对象，并在运行时根据需要选择使用哪一个策略。&lt;/p>
&lt;h2 id="责任链模式">
责任链模式
&lt;a href="#%e8%b4%a3%e4%bb%bb%e9%93%be%e6%a8%a1%e5%bc%8f" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>前面我们提到，控件的事件体现出了观察者模式。其实不仅如此，WPF 在传统 C# 事件的基础上，还引入了路由事件（&lt;code>RoutedEvent&lt;/code>）这一概念。&lt;/p>
&lt;p>路由事件允许事件在控件的视觉树中沿着特定的路径进行传播，这种传播机制使得我们可以在父级控件中处理子级控件的事件，或者反过来。具体要看路由的方式是冒泡（Bubble）还是隧道（Tunnel）。&lt;/p>
&lt;p>但不管哪一种方式，我们都可以在事件的处理过程中形成一个责任链。对于这一点，最明显的体现方式就是对于 &lt;code>e.Handled&lt;/code> 的使用。比如我们触发了一个鼠标事件，并且希望在某个父级控件中处理这个事件，那么我们可以在到达该控件时将 &lt;code>e.Handled&lt;/code> 设置为 &lt;code>true&lt;/code>，从而阻止事件继续向上传播。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">private&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">OnMouseDown&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="n">sender&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">MouseButtonEventArgs&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">sender&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="n">Border&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 处理边框的鼠标按下事件&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Handled&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 阻止事件继续向上传播&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这一操作就和责任链模式不谋而合：我们可以将事件的处理过程看作是一个链条，每个处理节点都可以选择是否将事件继续传递下去，从而形成一个灵活的事件处理机制。&lt;/p>
&lt;h2 id="其他设计模式">
其他设计模式
&lt;a href="#%e5%85%b6%e4%bb%96%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>除了上面这些典型的例子外，WPF 还体现出了很多其他的设计模式，比如：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>状态模式&lt;/strong>：控件的视觉状态（如鼠标悬停、按下等）可以通过 VisualStateManager 进行管理，这实际上就是一种状态模式的应用。&lt;/li>
&lt;li>&lt;strong>组合模式&lt;/strong>：WPF 的控件树结构使得我们可以将多个控件组合成一个复合控件，这正是组合模式的体现。&lt;/li>
&lt;li>&lt;strong>单例模式&lt;/strong>：&lt;code>Application.Current&lt;/code> 就是一个单例模式的实现。&lt;/li>
&lt;li>&lt;strong>原型模式&lt;/strong>：WPF 中资源的 &lt;code>Freezable&lt;/code> 以及 &lt;code>Style&lt;/code> 被多个控件使用，都体现出了对于原型实例的“克隆”。&lt;/li>
&lt;/ul>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在软件开发中，设计模式为我们提供了高效、灵活的解决方案，帮助提升代码的可维护性和可扩展性。本文通过分析 WPF 中的几个经典设计模式，如观察者模式、桥接模式、装饰器模式、适配器模式和责任链模式，展示了这些模式如何在 WPF 框架中得以体现。&lt;/p>
&lt;p>通过控件的事件机制、依赖属性、附加属性等机制，WPF 为开发者提供了丰富的设计模式支持，帮助开发者更好地组织和扩展应用程序。除此之外，WPF 还体现了状态模式、组合模式、单例模式等其他设计模式，为开发者提供了多种优秀的架构思想。&lt;/p></description></item><item><title>借助 ObservableCollections 获得更多具有通知功能的集合类型</title><link>https://blog.coldwind.top/posts/more-observable-collections/</link><pubDate>Fri, 11 Jul 2025 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/more-observable-collections/</guid><description>&lt;p>如果大家在做基于 C# 的 WPF、Avalonia、Win UI 等开发，尤其是遵循 MVVM 模式时，遇到过下面的这些烦恼：&lt;/p>
&lt;ol>
&lt;li>&lt;code>ObservableCollection&lt;/code> 没有批量操作的功能（例如 &lt;code>AddRange&lt;/code>）&lt;/li>
&lt;li>缺少 &lt;code>ObservableDictionary&lt;/code>、&lt;code>ObservableSet&lt;/code>、&lt;code>ObservableQueue&lt;/code> 等集合类型&lt;/li>
&lt;li>难以实现诸如过滤、映射等功能&lt;/li>
&lt;/ol>
&lt;p>那么，&lt;code>ObservableCollections&lt;/code> 这个 NuGet 包一定可以帮到你。没错，它就提供了一系列实用的具有通知功能的集合类型，使我们在 WPF、Avalonia、甚至 Unity 开发中都能够用得上。&lt;/p>
&lt;h2 id="安装">
安装
&lt;a href="#%e5%ae%89%e8%a3%85" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>它的源代码链接在 &lt;a class="link" href="https://github.com/Cysharp/ObservableCollections" target="_blank" rel="noopener"
>GitHub&lt;/a> 上。&lt;/p>
&lt;p>如果想要安装它，我们只需要在 NuGet 包管理器中搜索 &lt;code>ObservableCollections&lt;/code>，或者直接在项目中运行以下命令：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">dotnet add package ObservableCollections
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>安装之后即可使用。注意它还有一个结尾包含 &lt;code>R3&lt;/code> 的版本，是为它开发的 R3 库而准备的，通常我们不需要使用。这个 R3 库简单来说，就是一个更加高性能的 Rx.NET。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">说起它的开发者 &lt;a class="link" href="https://github.com/Cysharp" target="_blank" rel="noopener"
>Cysharp&lt;/a>，那可真的可以说是如雷贯耳。比如他们开发的 &lt;code>Unitask&lt;/code>，就是一个非常流行的适用于 Unity 的异步编程库；而他们开发的 &lt;code>ZLinq&lt;/code>，最近也是非常有名。油管上的 Nick Chapsas 也 &lt;a class="link" href="https://www.youtube.com/watch?v=pUBc9uutSZM" target="_blank" rel="noopener"
>曾经介绍过这个库&lt;/a>。&lt;/div>
&lt;/div>
&lt;h2 id="支持批量操作的可观测集合">
支持批量操作的可观测集合
&lt;a href="#%e6%94%af%e6%8c%81%e6%89%b9%e9%87%8f%e6%93%8d%e4%bd%9c%e7%9a%84%e5%8f%af%e8%a7%82%e6%b5%8b%e9%9b%86%e5%90%88" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>我们先来看一看它最简单的 &lt;code>ObservableList&lt;/code>。它是一个具有通知功能的列表，并且支持批量操作。我们只需要实例化，然后就可以使用它了：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MainViewModel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">ObservableList&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_items&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">MainViewModel&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 添加单个元素&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Item 1&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 批量添加元素&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AddRange&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">GetItems&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="n">IEnumerable&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">GetItems&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 假定这个方法可以从某个数据源获取一些数据&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>通过观察可以发现，这个集合类型提供了 &lt;code>CollectionChanged&lt;/code> 事件，我们可以通过订阅它来监听集合的变化。但是先不要想当然地认为它和 &lt;code>ObservableCollection&lt;/code> 一样。实际上，它的 &lt;code>CollectionChanged&lt;/code> 事件并不是来自我们熟悉的那个 &lt;code>INotifyCollectionChanged&lt;/code> 接口，而是这个库自带的一个接口。所以我们在上面的代码中，并没有直接将这个集合声明为 &lt;code>public&lt;/code> 的属性，从而在 XAML 中绑定。&lt;/p>
&lt;p>那么它为什么要这样做，让我们不能方便地使用呢？其实原因很简单：这个库不单单适用于 WPF，它还可以用于 Avalonia、Unity 等框架。为了兼容更多的框架，它就没有使用 &lt;code>INotifyCollectionChanged&lt;/code> 接口，而是提供了一个更通用的接口。&lt;/p>
&lt;p>但不必担心，它并没有止步于此，而是专门提供了方便我们在 WPF、Avalonia 等框架中使用的额外类型。简单来说，我们只需要调用它的下面这个方法，即可将它转为可以用于 XAML 绑定的集合对象：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MainViewModel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">ObservableList&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_items&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">INotifyCollectionChangedSynchronizedViewList&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">Items&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">MainViewModel&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Items&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">_items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToNotifyCollectionChanged&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里的 &lt;code>INotifyCollectionChangedSynchronizedViewList&lt;/code> 就继承了 &lt;code>INotifyCollectionChanged&lt;/code> 接口，因此实现了该接口的对象就可以直接在 XAML 中绑定使用，例如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ListBox&lt;/span> &lt;span class="na">ItemsSource=&lt;/span>&lt;span class="s">&amp;#34;{Binding Items}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>接下来，我们只需要在后台操作 &lt;code>_items&lt;/code> 集合，它的变化即可同步到 &lt;code>Items&lt;/code> 集合中，从而在 UI 上自动更新。&lt;/p>
&lt;h2 id="创建集合视图">
创建集合视图
&lt;a href="#%e5%88%9b%e5%bb%ba%e9%9b%86%e5%90%88%e8%a7%86%e5%9b%be" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>实际上，对于 &lt;code>ObservableList&lt;/code>，我们除了可以使用 &lt;code>ToNotifyCollectionChanged&lt;/code> 方法将其转换为可以用于 XAML 绑定的集合类型外，还可以使用 &lt;code>ToNotifyCollectionChangedSlim&lt;/code> 方法，将它转为一个更加轻量级的集合类型。这个类型同样实现了 &lt;code>INotifyCollectionChanged&lt;/code> 接口，但它的性能更高，适用于需要频繁更新的场景。代价是，它将不提供 &lt;code>AddRange&lt;/code> 等批量操作方法。&lt;/p>
&lt;p>这时候可能有同学就会问了：我用 &lt;code>ObservableList&lt;/code> 而不是原生的 &lt;code>ObservableCollection&lt;/code>，不就是为了它提供的批量操作方法吗？如果我不需要批量操作，直接用 &lt;code>ObservableCollection&lt;/code> 不就行了吗？&lt;/p>
&lt;p>这就引出了我们即将介绍的下一个功能，同时也是这个库相当重要的功能：&lt;code>View&lt;/code>。这个 &lt;code>View&lt;/code> 不是我们常说的 MVVM 中的视图，而是指对集合的视图。它可以让我们在不改变原始集合的情况下，对集合进行过滤、映射等操作。我们不需要关注视图的实现细节，只需要操作后台的集合，即可将更改同步到界面中。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MainViewModel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">ObservableList&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_items&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">ISynchronizedView&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_syncView&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">INotifyCollectionChangedSynchronizedViewList&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">Items&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">MainViewModel&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_syncView&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">_items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CreateView&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToUpper&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Items&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">_syncView&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToNotifyCollectionChanged&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">ToggleFilter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">bool&lt;/span> &lt;span class="n">useFilter&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">useFilter&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_syncView&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AttachFilter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">StartsWith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;A&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span> &lt;span class="c1">// 过滤以 &amp;#34;A&amp;#34; 开头的元素&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_syncView&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ResetFilter&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 清除过滤器&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">如果我们只是想使用映射功能，那么使用 &lt;code>ToNotifyCollectionChanged&lt;/code> 方法即可。它有一个重载，可以传入一个表示映射方式的 &lt;code>Func&lt;/code>。另外，它还支持传入一个类似 &lt;code>Dispatcher&lt;/code> 的参数，用于在 UI 线程上执行映射操作。至于为什么不是 WPF 中的 &lt;code>Dispatcher&lt;/code>，而是一个它自己声明的类型，这也是为了兼容更多的框架。&lt;/div>
&lt;/div>
&lt;p>在上面的代码中，我们创建了一个视图 &lt;code>_syncView&lt;/code>。在创建时，我们就指定了一个映射函数，将集合中的每个元素转换为大写形式。然后在 &lt;code>ToggleFilter&lt;/code> 方法中，我们可以通过 &lt;code>AttachFilter&lt;/code> 及 &lt;code>ResetFilter&lt;/code> 方法来添加或移除过滤器。就这样，我们轻松地实现了对集合的过滤和映射功能。&lt;/p>
&lt;p>简单想象一下，这些功能在 WPF、Avalonia 等框架中原本实现起来会多么麻烦。对于映射，我们可以借助 &lt;code>DataTemplate&lt;/code> 以及 &lt;code>ValueConverter&lt;/code> 来实现；而对于过滤，我们可能需要使用 &lt;code>CollectionView&lt;/code> 或者 &lt;code>ICollectionView&lt;/code> 等。这些都需要我们编写大量的样板代码。&lt;/p>
&lt;h2 id="可观测的字典">
可观测的字典
&lt;a href="#%e5%8f%af%e8%a7%82%e6%b5%8b%e7%9a%84%e5%ad%97%e5%85%b8" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>WPF 中其实有一个 &lt;a class="link" href="https://source.dot.net/#PresentationFramework/MS/Internal/Annotations/ObservableDictionary.cs" target="_blank" rel="noopener"
>&lt;code>ObservableDictionary&lt;/code>&lt;/a>，但它并不是 &lt;code>public&lt;/code> 的，只是标准库内部使用。或许我们可以使用一个 &lt;code>ObservableCollection&amp;lt;KeyValuePair&amp;lt;TKey, TValue&amp;gt;&amp;gt;&lt;/code> 来模拟一个字典，但这效率并不高，因为字典的添加、删除、查找等操作都是 $O(1)$ 的，而 &lt;code>ObservableCollection&lt;/code> 的这些操作都是 $O(n)$ 的。&lt;/p>
&lt;p>至于 Avalonia，我们就比较幸运了，它直接提供了 &lt;code>AvaloniaList&lt;/code> 和 &lt;code>AvaloniaDictionary&lt;/code>，这两个集合类型，前者支持批量操作，后者则是一个可观测的字典。&lt;/p>
&lt;p>下面我们用一个简单的例子来演示如何使用这个 &lt;code>ObservableDictionary&lt;/code>。它的使用方式和 &lt;code>ObservableList&lt;/code> 类似，我们只需要实例化它，然后就可以使用了：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MainViewModel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">ObservableDictionary&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_items&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">INotifyCollectionChangedSynchronizedViewList&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">Items&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">MainViewModel&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Items&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">_items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToNotifyCollectionChanged&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pair&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">pair&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Value&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Key1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Value1&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="可观测的队列">
可观测的队列
&lt;a href="#%e5%8f%af%e8%a7%82%e6%b5%8b%e7%9a%84%e9%98%9f%e5%88%97" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>队列有时候也是一个我们用得上的集合类型。它的特点是先进先出（FIFO），适用于需要按照顺序处理元素的场景。比如我们希望存储一些实时的消息，并且希望仅展示最新的几十条，而当超过这个数量时，自动删除最旧的消息。这就要求我们需要能够高效地删除队列头部的元素。这对于传统的列表来说是比较麻烦的，因为这会引入 $O(n)$ 的时间复杂度。&lt;/p>
&lt;p>&lt;code>ObservableCollections&lt;/code> 库提供了一个 &lt;code>ObservableQueue&amp;lt;T&amp;gt;&lt;/code>，不过我不打算详细介绍它，因为我们上面提到的需求有一个更加合适的集合类型，等下就会介绍到。但这里我们还是用一个简单的例子来演示它的用法：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MainViewModel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">ObservableQueue&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">LogMessage&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_logQueue&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">INotifyCollectionChangedSynchronizedViewList&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">LogMessage&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">LogMessages&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">MainViewModel&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">LogMessages&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">_logQueue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToNotifyCollectionChanged&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 添加日志消息&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">AddLogMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Application started&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">AddLogMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">logMessage&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">LogMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Now&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;o&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 如果队列超过 100 条，则删除最旧的消息&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">_logQueue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Count&lt;/span> &lt;span class="p">&amp;gt;=&lt;/span> &lt;span class="m">100&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_logQueue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Dequeue&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_logQueue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Enqueue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">logMessage&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">record&lt;/span> &lt;span class="nc">LogMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">Timestamp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Content&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">事实上，如果我们的需求只是比如说保留最近的几十到一百条消息，那么直接使用传统的 &lt;code>ObservableCollection&lt;/code> 也是完全可以接受的。虽然有点性能损失，但对于现在的 CPU 来说，这点复杂度完全是微不足道的。通过简单的 Benchmark，我们可以看到，&lt;code>List&lt;/code> 可能只比 &lt;code>Queue&lt;/code> 慢 50% 左右；甚至当数据量比较小（例如十几条）时，&lt;code>List&lt;/code> 更是能在性能上超过 &lt;code>Queue&lt;/code>。另外，&lt;code>List&lt;/code> 的使用显然比 &lt;code>Queue&lt;/code> 简单了不少。&lt;/div>
&lt;/div>
&lt;h2 id="可观测的环形缓冲区">
可观测的环形缓冲区
&lt;a href="#%e5%8f%af%e8%a7%82%e6%b5%8b%e7%9a%84%e7%8e%af%e5%bd%a2%e7%bc%93%e5%86%b2%e5%8c%ba" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>下面我们要介绍的这个集合类型，正是这个包最推荐我们用来实现这个保留最近的一些消息的集合类型：&lt;code>RingBuffer&lt;/code>。它是一个环形缓冲区，具有固定的大小。当添加新元素时，如果缓冲区已满，则会覆盖最旧的元素。这使得它非常适合用于存储最近的消息或数据。&lt;/p>
&lt;p>这个包提供了两种环形缓冲区：&lt;code>ObservableRingBuffer&lt;/code> 和 &lt;code>ObservableFixedSizeRingBuffer&lt;/code>。前者支持动态调整大小，而后者则是一个固定大小的环形缓冲区。借助后者，我们前面的例子可以简化为：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MainViewModel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">ObservableFixedSizeRingBuffer&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">LogMessage&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_logBuffer&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">100&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">INotifyCollectionChangedSynchronizedViewList&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">LogMessage&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">LogMessages&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">MainViewModel&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">LogMessages&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">_logBuffer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToNotifyCollectionChanged&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 添加日志消息&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">AddLogMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Application started&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">AddLogMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">logMessage&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">LogMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Now&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;o&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_logBuffer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">logMessage&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 添加新消息，自动覆盖最旧的消息&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>就这样，我们轻松地实现了一个保留最近 100 条日志消息的集合。&lt;/p>
&lt;h2 id="线程安全">
线程安全
&lt;a href="#%e7%ba%bf%e7%a8%8b%e5%ae%89%e5%85%a8" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>接下来这个部分相当重要，也是大家在使用这个包时需要尤其注意的，就是关于线程安全的问题。首先，这个包提供的每个集合都是线程安全的。它们内部会用一个线程锁，保证它的添加、删除等操作是线程安全的。但这并不意味着我们就可以高枕无忧了，因为虽然这些集合线程安全，但是从它们创建出的视图在同步它们的修改时，可能出现线程安全问题。那么，我们该怎么办呢？&lt;/p>
&lt;p>首先，在使用 &lt;code>ToNotifyCollectionChanged&lt;/code> 方法时，我们可以传入一个 &lt;code>Dispatcher&lt;/code> 参数。前面提到，这个参数是该类库自己声明的类型。但是它提供了一个方便我们使用的单例：&lt;code>SynchronizationContextCollectionEventDispatcher.Current&lt;/code>。借助它，我们就可以确保该方法创建出的视图在 UI 线程上执行修改操作，从而避免线程安全问题。&lt;/p>
&lt;p>但是这还不够。实测发现，虽然背后的集合本身线程安全，但是它创建出来的视图在操作时仍面临着线程安全问题。尤其是数据不一致。比如我们在删除元素之后立刻添加了元素，那么这两次动作在同步到视图的过程中就可能会出现问题。对于这个问题，如果我们确实有在多线程上操作背后集合的需求，那么我们可以考虑让这些操作都发生在主线程上。以 WPF 为例，我们可以这样：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MainViewModel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">ObservableList&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_items&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">INotifyCollectionChangedSynchronizedViewList&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">Items&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">MainViewModel&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Items&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">_items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToNotifyCollectionChanged&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">SynchronizationContextCollectionEventDispatcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Current&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">AddItem&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">item&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 确保在 UI 线程上执行添加操作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Application&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Current&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Dispatcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">InvokeAsync&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">_items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">RemoveItem&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">item&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 确保在 UI 线程上执行删除操作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Application&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Current&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Dispatcher&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">InvokeAsync&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">_items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Remove&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>当然，在 &lt;code>ViewModel&lt;/code> 中访问 &lt;code>Application.Current&lt;/code> 可能并不是一个十分遵守 MVVM 模式的好习惯。因此在更加严谨的项目中，我们可以考虑将 &lt;code>Dispatcher&lt;/code> 作为参数传入 &lt;code>ViewModel&lt;/code>，或者使用依赖注入的方式来获取它。这样可以更好地遵循 MVVM 模式，同时也能确保在 UI 线程上执行操作。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>&lt;code>ObservableCollections&lt;/code> 是一个非常实用的 NuGet 包，它提供了多种具有通知功能的集合类型，适用于 WPF、Avalonia、Win UI 等框架。它不仅支持批量操作，还提供了过滤、映射等功能，使得我们在开发中可以更加高效地处理集合数据。&lt;/p>
&lt;p>在使用时，我们需要注意线程安全问题，尤其是在多线程环境下操作集合时。通过合理地使用 &lt;code>Dispatcher&lt;/code>，我们可以确保集合的操作在 UI 线程上执行，从而避免数据不一致的问题。&lt;/p></description></item><item><title>.NET 原生有哪些 Timer 以及它们分别是怎么用的？</title><link>https://blog.coldwind.top/posts/how-many-timers-are-there/</link><pubDate>Mon, 07 Jul 2025 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/how-many-timers-are-there/</guid><description>&lt;p>相信很多 .NET 新手（甚至有几年经验的老手）都会搞不清楚这个问题：.NET 原生有哪些计时器（Timer）？它们分别是做什么用的？该如何选择以及如何正确地使用？&lt;/p>
&lt;p>这篇文章我们就来盘点一下吧。&lt;/p>
&lt;h2 id="一共有多少种-timer">
一共有多少种 Timer？
&lt;a href="#%e4%b8%80%e5%85%b1%e6%9c%89%e5%a4%9a%e5%b0%91%e7%a7%8d-timer" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>首先我们来回答一下这个问题。在 &lt;a class="link" href="https://source.dot.net" target="_blank" rel="noopener"
>.NET 源代码&lt;/a> 中搜索 &lt;code>Timer&lt;/code>，我们可以找到答案。排除掉一些 &lt;code>internal&lt;/code> 或 &lt;code>abstract&lt;/code> 的类型（例如 &lt;code>System.Net.Timer&lt;/code>、&lt;code>Microsoft.ML.Trainers.FastTree.Timer&lt;/code> 等），我们可以找到以下几种计时器：&lt;/p>
&lt;ul>
&lt;li>&lt;code>System.Threading.Timer&lt;/code>&lt;/li>
&lt;li>&lt;code>System.Timers.Timer&lt;/code>&lt;/li>
&lt;li>&lt;code>System.Threading.PeriodicTimer&lt;/code>&lt;/li>
&lt;li>&lt;code>System.Windows.Threading.DispatcherTimer&lt;/code>&lt;/li>
&lt;li>&lt;code>System.Windows.Forms.Timer&lt;/code>&lt;/li>
&lt;li>&lt;code>System.Web.UI.Timer&lt;/code>&lt;/li>
&lt;li>&lt;code>Windows.UI.Xaml.DispatcherTimer&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>这里，后面四个可以从命名空间看出，它们适用于特定的 UI 框架（即 WPF、WinForms、ASP.NET Forms、Win UI 等），而前面三个则是更通用的计时器，适用于大多数场景。这篇文章我们主要介绍前三个，并且在后四个中选择适用于 WPF 的 &lt;code>DispatcherTimer&lt;/code> 进行介绍。&lt;/p>
&lt;h2 id="systemthreadingtimer">
System.Threading.Timer
&lt;a href="#systemthreadingtimer" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>源代码：&lt;a class="link" href="https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs" target="_blank" rel="noopener"
>System.Threading.Timer.cs&lt;/a>&lt;/p>
&lt;p>&lt;code>System.Threading.Timer&lt;/code> 是 .NET 中最常用也是最轻量的计时器之一。它是基于线程池的，所以不与某个特定线程（如 UI 线程）关联，并且也不会阻塞调用线程。&lt;/p>
&lt;p>它没有提供诸如 &lt;code>Start&lt;/code> 和 &lt;code>Stop&lt;/code> 方法，而是通过设置回调函数和周期来启动（还可以通过 &lt;code>Change&lt;/code> 方法来调整周期）。当不需要使用时，可以通过调用 &lt;code>Dispose&lt;/code> 方法来结束它并释放资源。&lt;/p>
&lt;p>下面是一个简单的例子：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Threading&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 第三个参数是初始延迟时间，第四个参数是周期时间（单位都是毫秒）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 这里的传参意味着，计时器将会没有初始延迟，且每隔 1 秒执行一次回调函数&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">timer&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Timer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">TimerCallback&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">1000&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Timer started. Press Enter to exit...&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ReadLine&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">timer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Dispose&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Timer stopped and disposed.&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">void&lt;/span> &lt;span class="n">TimerCallback&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object?&lt;/span> &lt;span class="n">state&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">$&amp;#34;Timer callback executed at {DateTime.Now}, thread id: {Environment.CurrentManagedThreadId}&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>输出结果形如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-plaintext" data-lang="plaintext">&lt;span class="line">&lt;span class="cl">Timer started. Press Enter to exit...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Timer callback executed at 2025/7/6 19:27:27, thread id: 11
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Timer callback executed at 2025/7/6 19:27:28, thread id: 9
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Timer callback executed at 2025/7/6 19:27:29, thread id: 9
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Timer callback executed at 2025/7/6 19:27:30, thread id: 9
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Timer stopped and disposed.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我们不难发现几个现象：&lt;/p>
&lt;ol>
&lt;li>计时器在创建后立刻就开始执行了，不需要调用类似 &lt;code>Start&lt;/code> 的方法；&lt;/li>
&lt;li>计时器没有阻塞创建它的线程，它类似于启动了一个后台服务；&lt;/li>
&lt;li>计时器的回调函数是在不同的线程上执行的，而且每次执行的线程 ID 可能不同，这取决于线程池的调度；&lt;/li>
&lt;li>计时器可以通过 &lt;code>Dispose&lt;/code> 方法来停止及释放资源。&lt;/li>
&lt;/ol>
&lt;p>因为它的一些局限性，这在实际开发中可能会让我们遇到一些困难，比如我们无法灵活地控制它的开始与结束，以及暂停和重启等。另外，因为它每次的回调可能都发生在不同的线程上，所以我们需要特别注意线程安全问题，尤其是在访问共享资源，或者需要某些操作发生在特定线程（如 UI 线程）时。&lt;/p>
&lt;p>关于这些问题，我们会在后续介绍的其他计时器中看到更好的解决方案。&lt;/p>
&lt;h2 id="systemtimerstimer">
System.Timers.Timer
&lt;a href="#systemtimerstimer" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>源代码：&lt;a class="link" href="https://source.dot.net/#System.ComponentModel.TypeConverter/System/Timers/Timer.cs" target="_blank" rel="noopener"
>System.Timers.Timer.cs&lt;/a>&lt;/p>
&lt;p>&lt;code>System.Timers.Timer&lt;/code> 是一个更高级的计时器，它基于（或者可以理解为封装了） &lt;code>System.Threading.Timer&lt;/code>，并提供了更多的功能和更易用的 API。比如它提供了开始、停止、关闭等功能，还提供了一些属性来控制计时器的行为，比如：&lt;/p>
&lt;ul>
&lt;li>Interval：设置计时器的间隔时间（毫秒），不再需要使用 &lt;code>Change&lt;/code> 方法了；&lt;/li>
&lt;li>Enabled：设置计时器是否启用（&lt;code>Start&lt;/code> 和 &lt;code>Stop&lt;/code> 方法其实就是在控制它）；&lt;/li>
&lt;li>AutoReset：设置计时器是否自动重置（即是否在回调函数执行完毕后立即重新开始计时，默认为 &lt;code>true&lt;/code>）。或者换一种理解方式，有时候我们不希望计时器会每周期都触发一次，而是真的像一个简单的定时器那样，在开始后到达设定的周期就触发，然后停在那里，等待下一次启动。&lt;/li>
&lt;/ul>
&lt;p>下面是一个简单的例子：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Timers&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">timer&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Timer&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 创建一个计时器（默认的周期为 100 毫秒）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">timer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Elapsed&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">TimerElapsedHandler&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 订阅 Elapsed 事件&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">timer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Interval&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">1000&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 设置间隔为 1 秒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">timer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Start&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Timer started. Press Enter to exit...&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ReadLine&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">timer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Stop&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Timer stopped and disposed.&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">timer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Dispose&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">void&lt;/span> &lt;span class="n">TimerElapsedHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object?&lt;/span> &lt;span class="n">sender&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ElapsedEventArgs&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">$&amp;#34;Timer elapsed at {e.SignalTime}, thread id: {Environment.CurrentManagedThreadId}&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>现在我们可以稍微探讨一下这个计时器的另外一个特性了：如果它的回调函数比较耗时，甚至超过了它的周期，会怎么样？&lt;/p>
&lt;p>答案非常简单：计时器依旧会按照设定的周期继续触发回调函数，虽然看起来（比如从控制台的输出）可能会表现出延迟，甚至可能因为每次回调的延迟不同而使得输出顺序变得混乱。这也就是它使用线程池的原因之一：即便上一次回调还没有完成，导致它所在的线程仍处于阻塞状态，下一次回调依旧可以在其他线程上继续执行。&lt;/p>
&lt;div class="notice note">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-sticky-note" aria-hidden="true">&lt;/i>Note
&lt;/div>
&lt;div class="notice-content">还有一个值得注意的点：当计时器停止（甚至释放）后，之前每次 &lt;code>Elapsed&lt;/code> 触发的回调如果还没有执行完毕，那么将仍会处于执行状态，尤其是它们内部有耗时的操作时。这是因为计时器每次触发时，都会将回调函数放入线程池中执行，而线程池中的线程会继续执行这些任务，直到它们完成。&lt;/div>
&lt;/div>
&lt;h2 id="systemthreadingperiodictimer">
System.Threading.PeriodicTimer
&lt;a href="#systemthreadingperiodictimer" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>源代码：&lt;a class="link" href="https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Threading/PeriodicTimer.cs" target="_blank" rel="noopener"
>System.Threading.PeriodicTimer.cs&lt;/a>&lt;/p>
&lt;p>这是一个比较新的计时器（.NET 6+），它不仅现代，而且精确，还支持异步操作。正如它的名称所提示的，它旨在提供一个周期性的计时器，允许我们在每个周期结束时执行一个异步操作。它与传统的 &lt;code>Timer&lt;/code> 类不同，不使用事件或回调，而是通过 &lt;code>await&lt;/code> 一个异步方法来控制每次操作的发生。&lt;/p>
&lt;p>它的使用方式也非常简单，下面是一个例子：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Threading&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">timer&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">PeriodicTimer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">TimeSpan&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FromSeconds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">));&lt;/span> &lt;span class="c1">// 创建一个周期为 1 秒的计时器&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">cts&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">CancellationTokenSource&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">token&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">cts&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Token&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">await&lt;/span> &lt;span class="n">timer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WaitForNextTickAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">token&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">token&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ThrowIfCancellationRequested&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">$&amp;#34;Periodic timer tick at {DateTime.Now}, thread id: {Environment.CurrentManagedThreadId}&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">OperationCanceledException&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Periodic timer canceled.&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">finally&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">timer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Dispose&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cts&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Dispose&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这个计时器还有一个常见的使用情形，就是在 ASP.NET Core 中借助它来创建一个后台的定时任务。因为它不仅准时，而且支持异步操作。比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyService&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">BackgroundService&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">ILogger&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">MyService&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">logger&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">PeriodicTimer&lt;/span> &lt;span class="n">timer&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">MyService&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ILogger&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">MyService&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">logger&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">logger&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">logger&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">timer&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">TimeSpan&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FromMilliseconds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1000&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">protected&lt;/span> &lt;span class="kd">override&lt;/span> &lt;span class="kd">async&lt;/span> &lt;span class="n">Task&lt;/span> &lt;span class="n">ExecuteAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CancellationToken&lt;/span> &lt;span class="n">stoppingToken&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">await&lt;/span> &lt;span class="n">timer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WaitForNextTickAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">stoppingToken&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="n">stoppingToken&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IsCancellationRequested&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">logger&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">LogInformation&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Hello, world!&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后我们就可以在入口处注册这个服务了：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">builder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Services&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AddHostedService&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">MyService&lt;/span>&lt;span class="p">&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样即便每次循环体中的操作比较耗时，它仍然可以保证每次触发的时间是准确的。它绝对比在循环中使用 &lt;code>await Task.Delay()&lt;/code> 要准确得多。&lt;/p>
&lt;h2 id="systemwindowsthreadingdispatchertimer">
System.Windows.Threading.DispatcherTimer
&lt;a href="#systemwindowsthreadingdispatchertimer" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>源代码：&lt;a class="link" href="https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherTimer.cs" target="_blank" rel="noopener"
>DispatcherTimer.cs&lt;/a>&lt;/p>
&lt;p>最后我们再来简单地看一下适用于 WPF 的 &lt;code>DispatcherTimer&lt;/code>。看到 &lt;code>Dispatcher&lt;/code> 这个词，我们很容易联想到诸如 &lt;code>Application.Current.Dispatcher&lt;/code>，所以它主要用于在 UI 线程上执行操作。它的使用方式与 &lt;code>System.Timers.Timer&lt;/code> 类似，也提供了 &lt;code>Start&lt;/code>、&lt;code>Stop&lt;/code> 等方法，以及 &lt;code>Interval&lt;/code> 属性和 &lt;code>Tick&lt;/code> 事件等。&lt;/p>
&lt;p>下面是一个简单的例子：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Windows.Threading&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">partial&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MainWindow&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">Window&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">DispatcherTimer&lt;/span> &lt;span class="n">timer&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">MainWindow&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">InitializeComponent&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">timer&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">DispatcherTimer&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">timer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Interval&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">TimeSpan&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FromSeconds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">timer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Tick&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">Timer_Tick&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">timer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Start&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Timer_Tick&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="n">sender&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">EventArgs&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">listBox&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">$&amp;#34;Dispatcher timer tick at {DateTime.Now}, thread id: {Environment.CurrentManagedThreadId}&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>DispatcherTimer&lt;/code> 有几个构造函数，可以指定它的优先级以及所使用的 &lt;code>Dispatcher&lt;/code>。默认情况下，它会使用 &lt;code>DispatcherPriority.Background&lt;/code> 以及 &lt;code>Dispatcher.Current&lt;/code>。只要你在 UI 线程上创建它，它就会在 UI 线程上执行回调函数。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在这篇文章中，我们介绍了 .NET 中常用的几种计时器，包括它们各自的功能和特点，以及所适合的场景。简单来说：&lt;/p>
&lt;ul>
&lt;li>&lt;code>System.Threading.Timer&lt;/code> 是最轻量的计时器，适用于大多数非 UI 线程的场景，但因为缺少灵活的控制方法和线程安全问题，可能需要一些额外的处理；&lt;/li>
&lt;li>&lt;code>System.Timers.Timer&lt;/code> 提供了更易用的 API 和更多的功能，适用于大多数需要定时操作的场景；&lt;/li>
&lt;li>&lt;code>System.Threading.PeriodicTimer&lt;/code> 是一个现代的计时器，支持异步操作，适用于需要精确控制周期性操作的场景，以及异步编程；&lt;/li>
&lt;li>&lt;code>DispatcherTimer&lt;/code> 适用于 WPF，能够在 UI 线程上执行操作，适合需要与 UI 交互的场景。&lt;/li>
&lt;/ul>
&lt;p>希望这篇文章能帮助你更好地理解 .NET 中的计时器，并在实际开发中选择合适的计时器来满足你的需求。如果你有任何问题或建议，欢迎在评论区留言讨论！&lt;/p></description></item><item><title>常见与不常见哈希函数</title><link>https://blog.coldwind.top/posts/some-other-hash-functions/</link><pubDate>Mon, 30 Jun 2025 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/some-other-hash-functions/</guid><description>&lt;p>在 &lt;a class="link" href="../why-password-hash-with-salt" >前面的文章&lt;/a> 中，我们已经探讨了有关密码加盐哈希的话题。这次我们围绕着哈希函数再做一些补充。&lt;/p>
&lt;p>哈希函数是计算机科学和密码学中非常重要的工具。它们用于数据完整性验证、数字签名、密码存储等多个领域。哈希函数的种类有很多，它们有的是常见的，有的则相对不那么常用。本文将介绍一些常见和不常见的哈希函数，以及它们的特点和应用场景。&lt;/p>
&lt;h2 id="早期哈希函数">
早期哈希函数
&lt;a href="#%e6%97%a9%e6%9c%9f%e5%93%88%e5%b8%8c%e5%87%bd%e6%95%b0" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>说起早期的哈希函数，MD5 和 SHA-1 是最广为人知的。当然，在它们之前还有更早的，比如 MD4 和 SHA-0，但它们早已不再被广泛使用。MD5 和 SHA-1 在 1990 年代和本世纪初期非常流行，广泛应用于文件完整性校验、数字签名等场景。&lt;/p>
&lt;p>它们虽然曾被广泛使用，但由于安全性问题，现在已不再推荐用于安全敏感的应用。尽管如此，它们仍然非常流行，比如许多软件下载等场景都会提供文件的 MD5 或 SHA-1 校验和，以方便用户验证下载的文件是否完整。所以，即便一个技术有缺点（甚至有严重漏洞），只要它足够流行，仍然会被广泛使用。就像 JPG 图片格式一样，虽然它有很多缺点（比如不支持透明度、不支持无损压缩、容易出现伪影等），但它仍然是最常用的图片格式之一。&lt;/p>
&lt;p>那么它们到底有什么安全性问题呢？简单来说，MD5 和 SHA-1 都存在碰撞攻击的风险。碰撞攻击是指两个不同的输入数据经过哈希函数处理后，得到相同的哈希值。这意味着攻击者有可能构造一个恶意文件，使其哈希值与合法文件相同，从而绕过完整性验证。&lt;/p>
&lt;p>就拿上面提到的验证下载文件的完整性来说。比如网站告诉你，该文件的哈希值是 &lt;code>d41d&lt;/code>（这里为方便起见，仅使用前 4 位）。然后你下载了一个文件，计算出来的哈希值也是这个。一般来说，你可以确定这个文件没有被篡改，从而可以放心使用。但是黑客可以构造一个哈希值同样是 &lt;code>d41d&lt;/code> 的恶意文件，并借助一些手段向你提供这个文件。你下载后，就会误以为这个文件是安全的，最终运行了病毒程序。除了病毒程序，还可能是伪造的证书文件等。这些都可能带来严重的后果。&lt;/p>
&lt;p>即便如此，碰撞攻击依旧是成本巨大的。再加上 MD5 的广泛适配性以及高效的计算速度，使得它在很多场景下仍然被使用。比如在一些非安全敏感的应用中，MD5 仍然被用来快速计算文件的哈希值，以便进行完整性校验。&lt;/p>
&lt;p>说起哈希碰撞，这里还有一个有趣的例子。在 2017 年，谷歌的研究人员制作了下面这张 GIF 动图。这张动图神奇的地方在于，它可以展示自己的 MD5 值！&lt;/p>
&lt;p>&lt;img src="https://www.bleepstatic.com/images/news/u/1164866/2022/sep-2022/md5-image/md5.gif"
loading="lazy"
alt="Hashquines: files containing their own checksums"
>&lt;/p>
&lt;p>更多有意思的例子可以看看 &lt;a class="link" href="https://www.bleepingcomputer.com/news/security/this-image-shows-its-own-md5-checksum-and-its-kind-of-a-big-deal/" target="_blank" rel="noopener"
>这篇文章&lt;/a>。&lt;/p>
&lt;h2 id="sha-2-系列">
SHA-2 系列
&lt;a href="#sha-2-%e7%b3%bb%e5%88%97" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>因为 MD5 和 SHA-1 的安全性问题，SHA-2 系列（包括 SHA-256、SHA-384、SHA-512 等）成为了新的标准。SHA-2 系列的哈希函数在设计上更为复杂，提供了更高的安全性。它们被广泛应用于数字签名、证书颁发机构（CA）等领域。&lt;/p>
&lt;p>一般来说，如果我们想要将它用于数据库中存储用户的密码，那么通常还会给它加上一个随机的盐值（salt），这样可以防止彩虹表攻击。彩虹表攻击是指攻击者预先计算出大量常见密码的哈希值，并存储在一个表中。当攻击者获取到哈希值后，可以通过查找这个表来快速破解密码。通过添加盐值，即使两个用户的密码相同，它们的哈希值也会不同，从而增加了破解的难度。&lt;/p>
&lt;p>以 C# 为例，使用 SHA-256 哈希函数和盐值来存储密码的代码如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Text&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Security.Cryptography&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">GenerateSalt&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">rng&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">RNGCryptoServiceProvider&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">salt&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="m">16&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">rng&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetBytes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">salt&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">salt&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 上面的方法会提示已过时，可以使用下面的方式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// return RandomNumberGenerator.GetBytes(16);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">HashPassword&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">password&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">salt&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">sha256&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">SHA256&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Create&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">passwordBytes&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Encoding&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">UTF8&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetBytes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">saltedPassword&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">passwordBytes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="n">salt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Buffer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">BlockCopy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">passwordBytes&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">saltedPassword&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">passwordBytes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Buffer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">BlockCopy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">salt&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">saltedPassword&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">passwordBytes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">salt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">sha256&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ComputeHash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">saltedPassword&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 新版本还提供了更简便的静态方法，比如 SHA256.HashData&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后将加盐哈希后的密码与盐值一起存储到数据库中就可以了。需要验证密码时，先从数据库中获取盐值，然后使用相同的哈希函数和盐值对输入的密码进行哈希，再与存储的哈希值进行比较即可。&lt;/p>
&lt;p>不必担心盐值泄露的问题，因为即使攻击者获取了盐值，也无法直接破解密码。盐值的作用是增加哈希值的唯一性和复杂性，使得攻击者无法使用预先计算的彩虹表来破解密码。&lt;/p>
&lt;h2 id="sha-3-系列">
SHA-3 系列
&lt;a href="#sha-3-%e7%b3%bb%e5%88%97" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>SHA-2 系列虽然解决了 MD5 和 SHA-1 的安全性问题，但它仍然是基于与前代相同的架构（Merkle-Damgård）。科学家担心之前的碰撞方式继续发展和研究下去有可能破解 SHA-2 系列，并且随着量子计算的发展，SHA-2 系列的安全性也可能受到威胁。因此，NIST（美国国家标准与技术研究院）在 2015 年发布了 SHA-3 系列。&lt;/p>
&lt;p>SHA-3 系列采用了全新的设计理念，基于 Keccak 算法。它不仅提供了更高的安全性，还可以根据需要选择不同的输出长度（如 SHA3-224、SHA3-256、SHA3-384、SHA3-512 等）。&lt;/p>
&lt;p>不过 SHA-3 系列目前的应用并不算广泛，它更多的是作为 SHA-2 的一个备选，以便未来在 SHA-2 系列被破解时可以迅速切换到更安全的哈希函数。此外，虽然它提供了更好的安全性和灵活性，但是在实际的场景下，我们通常会选择其他的一些更擅长某一方面的方法。&lt;/p>
&lt;h2 id="pbkdf2">
PBKDF2
&lt;a href="#pbkdf2" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在 SHA-2 的基础上，为了进一步提高破解的难度，除了引入盐值外，通常还会引入迭代次数。PBKDF2（Password-Based Key Derivation Function 2）就是一个常用的密码哈希函数，它通过多次迭代哈希计算来增加破解的难度。&lt;/p>
&lt;p>PBKDF2 的工作原理是将密码和盐值作为输入，经过多次迭代的哈希计算，生成一个固定长度的输出。迭代次数越多，破解的难度就越大。PBKDF2 通常用于密码存储和密钥派生。&lt;/p>
&lt;p>在 C# 中，可以使用 &lt;code>Rfc2898DeriveBytes&lt;/code> 类来实现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Security.Cryptography&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Text&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">HashPasswordWithPBKDF2&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">password&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">salt&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">iterations&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">10000&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">pbkdf2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Rfc2898DeriveBytes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">salt&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">iterations&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">HashAlgorithmName&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SHA256&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">pbkdf2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetBytes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">32&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 生成 32 字节的哈希值&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="bcrypt-与-argon2">
bcrypt 与 Argon2
&lt;a href="#bcrypt-%e4%b8%8e-argon2" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>可惜的是，PBKDF2 在某些情况下可能不够安全，尤其是面对现代硬件的攻击（比如 GPU 超强的并行计算能力）。为了解决这个问题，出现了 bcrypt 和 Argon2 等更安全的密码哈希函数。&lt;/p>
&lt;p>bcrypt 是基于 Blowfish 加密算法的密码哈希函数，它通过增加计算复杂度来提高破解难度。bcrypt 的一个重要特性是它可以调整工作因子（cost factor），从而控制哈希计算的时间和资源消耗。工作因子越高，破解的难度就越大。与迭代次数不同的是，这个工作因子是指数级增长的，这意味着每增加一个单位的工作因子，计算时间就会翻倍。在 C# 中，可以使用 &lt;code>BCrypt.Net-Next&lt;/code> 等库来实现 bcrypt。&lt;/p>
&lt;p>Argon2 是 2015 年密码学竞赛的获胜者，它被认为是目前最安全的密码哈希函数之一。Argon2 具有高度的可配置性，可以调整内存使用量、迭代次数和并行度等参数，从而提供更强的安全性。Argon2 分为三个变种：Argon2d、Argon2i 和 Argon2id，分别针对不同的攻击场景。在 C# 中，可以使用 &lt;code>Konscious.Security.Cryptography.Argon2&lt;/code> 等库来使用 Argon2。&lt;/p>
&lt;h2 id="blake2">
BLAKE2
&lt;a href="#blake2" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>BLAKE2 是一个相对较新的哈希函数（2013 年发布），它在速度和安全性之间取得了很好的平衡。BLAKE2 的设计目标是提供比 MD5 和 SHA-1 更快的速度，同时比 SHA-2 更高的安全性。它非常适合用于文件完整性校验、密码哈希等场景。&lt;/p>
&lt;p>它的高性能得益于它充分利用了现代 CPU 的 SIMD 指令集（如 SSE2/AVX 等），在多核处理器上表现尤为出色。不仅如此，它还提供了两个主要版本：BLAKE2b 和 BLAKE2s。BLAKE2b 适用于 64 位平台，输出长度可变，最大为 64 字节；而 BLAKE2s 适用于 8 到 32 位平台，输出长度可变，最大为 32 字节。&lt;/p>
&lt;p>除此之外，它还提供了很多特性，比如内置密钥机制、可选盐值和个性化字符串等。这些特性使得 BLAKE2 在很多应用场景中都非常有用。&lt;/p>
&lt;p>在 C# 中，可以使用 &lt;code>BouncyCastle&lt;/code> 等库来实现 BLAKE2。以下是一个简单的示例：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">Org.BouncyCastle.Crypto&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">Org.BouncyCastle.Crypto.Digests&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">digest&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Blake2bDigest&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 默认 512，可以改为 8~512 的任意 8 的倍数&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">digest&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">BlockUpdate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">hash&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">digest&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetDigestSize&lt;/span>&lt;span class="p">()];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">digest&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">DoFinal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hash&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="sm3">
SM3
&lt;a href="#sm3" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>最后我们再来介绍一个国产的哈希函数：SM3。SM3 是中国国家密码管理局在 2010 年发布的哈希函数标准。它是中国独立设计和开发的哈希算法，不依赖于国外的标准。这对于国家安全和信息安全具有重要意义。&lt;/p>
&lt;p>SM3 算法生成 256 位的哈希值，并且安全性及效率与 SHA-256 相当。它在设计上具有良好的抗碰撞性和单向性，旨在抵抗各种密码分析攻击。&lt;/p>
&lt;p>作为中国的国家标准，SM3 在国内的应用越来越广泛，尤其是在金融、政府和军工等领域。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>哈希函数可谓是种类繁多、各有所长。从早期的 MD5 和 SHA-1，到现在的 SHA-2、SHA-3、PBKDF2、bcrypt、Argon2、BLAKE2 和 SM3，每种哈希函数都有其独特的设计理念和应用场景。&lt;/p>
&lt;p>简单来说，一些常见需求及可以选择的哈希函数如下：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>数据完整性校验&lt;/strong>：MD5、SHA-1、SHA-2、BLAKE2&lt;/li>
&lt;li>&lt;strong>密码存储&lt;/strong>：PBKDF2、bcrypt、Argon2&lt;/li>
&lt;li>&lt;strong>数字签名&lt;/strong>：SHA-2、SHA-3&lt;/li>
&lt;li>&lt;strong>国产安全&lt;/strong>：SM3&lt;/li>
&lt;/ul></description></item><item><title>逆向思考 .NET 一些版本的新特性</title><link>https://blog.coldwind.top/posts/reverse-thinking-of-dotnet-new-features/</link><pubDate>Tue, 03 Jun 2025 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/reverse-thinking-of-dotnet-new-features/</guid><description>&lt;p>.NET 作为一个近年来更新频率稳定的平台，每个版本都会引入一些新的特性和改进。这些新特性往往是为了提高开发效率、增强性能或改善用户体验。然而，很多时候我们可能会对某些新特性的引入感到疑惑，甚至认为它们并不是那么必要。&lt;/p>
&lt;p>在这篇文章中，我想借助几个例子来分享我的思考，并给大家提供一个有意思的视角：逆向思考 .NET 新特性背后的逻辑和思考方式。&lt;/p>
&lt;h2 id="匿名类型lambda-表达式和扩展方法">
匿名类型、lambda 表达式和扩展方法
&lt;a href="#%e5%8c%bf%e5%90%8d%e7%b1%bb%e5%9e%8blambda-%e8%a1%a8%e8%be%be%e5%bc%8f%e5%92%8c%e6%89%a9%e5%b1%95%e6%96%b9%e6%b3%95" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在 C# 3 中，.NET 引入了匿名类型、lambda 表达式和扩展方法等特性。我们先来简单回顾一下这些特性都是什么。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 匿名类型&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">mapped&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">people&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Select&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Age&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Lambda 表达式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">filtered&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">people&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Where&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Age&lt;/span> &lt;span class="p">&amp;gt;&lt;/span> &lt;span class="m">18&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 扩展方法&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">StringExtensions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="n">IEnumerable&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">Where&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="k">this&lt;/span> &lt;span class="n">IEnumerable&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">source&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Func&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">bool&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">predicate&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">source&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">predicate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="n">item&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>相信大家看了上面我“别有用心”的实例之后，一定不难发现：这些新特性似乎都与 LINQ 有关，而 LINQ 正是在 C# 3.0 中引入的。&lt;/p>
&lt;p>因此，我们不难得出结论：这些新特性都是为了支持 LINQ 的语法而添加的。它们使得我们可以更简洁地编写查询代码，提升了代码的可读性和可维护性。&lt;/p>
&lt;p>当然了，这三个特性绝不仅仅是为了支持 LINQ 而存在的。它们在其他场景下也有着广泛的应用。例如，匿名类型可以用于快速创建临时数据结构，lambda 表达式可以用于方便地声明事件处理和回调，而扩展方法则可以让我们为现有类型添加新的功能。它们都是相当强大的功能。&lt;/p>
&lt;h2 id="丢弃运算符">
丢弃运算符
&lt;a href="#%e4%b8%a2%e5%bc%83%e8%bf%90%e7%ae%97%e7%ac%a6" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>我们再来看一个例子。C# 7 引入了丢弃运算符（&lt;code>_&lt;/code>），它可以用于忽略不需要的值。这看起来似乎是一个小特性，但如果我们再去看这个版本引入的其他几个特性，就不难发现它们之间的关系了：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// C# 7.0-&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">int&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">TryParse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">input&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">out&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Tuple&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">GetResults&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">results&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">GetResults&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="k">value&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">results&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Item1&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 忽略 Item2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// C# 7.0+&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">TryParse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">input&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">out&lt;/span> &lt;span class="kt">var&lt;/span> &lt;span class="n">_&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">GetResults&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">_&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">GetResults&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 使用元组解构&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>不难发现，C# 7 还引入了新的 &lt;code>out&lt;/code> 变量声明和元组解构语法。而在这些语法中，丢弃运算符都可以起到便捷的作用。因此，我们可以说，这几个新的特性是密不可分的，所以它们也得以在这个版本中同时出现。&lt;/p>
&lt;h2 id="init-访问器与记录类">
&lt;code>init&lt;/code> 访问器与记录类
&lt;a href="#init-%e8%ae%bf%e9%97%ae%e5%99%a8%e4%b8%8e%e8%ae%b0%e5%bd%95%e7%b1%bb" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>C# 9 引入了 &lt;code>init&lt;/code> 访问器和记录类（record class）。记录类为我们提供了相当便捷的声明不可变数据类型的方式，并且重写了 &lt;code>Equals&lt;/code>、&lt;code>GetHashCode&lt;/code> 和 &lt;code>ToString&lt;/code> 等方法，来提供更好的语义。&lt;/p>
&lt;p>默认情况下，一个记录类中的属性都是只读的，即：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">record&lt;/span> &lt;span class="nc">Person&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">Name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Age&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 相当于&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Person&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">init&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Age&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">init&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">override&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">Equals&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object?&lt;/span> &lt;span class="n">obj&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="cm">/* ... */&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">override&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">GetHashCode&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="cm">/* ... */&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">override&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">ToString&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="cm">/* ... */&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>因此，&lt;code>init&lt;/code> 访问器与记录类一起出现是很自然的。它们共同提供了一种声明不可变数据类型的方式，使得我们可以更方便地创建和使用不可变对象。&lt;/p>
&lt;h2 id="顶级语句与隐式引入">
顶级语句与隐式引入
&lt;a href="#%e9%a1%b6%e7%ba%a7%e8%af%ad%e5%8f%a5%e4%b8%8e%e9%9a%90%e5%bc%8f%e5%bc%95%e5%85%a5" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>C# 9 引入了顶级语句，而 C# 10 引入了隐式引入。这样我们既不用再写 &lt;code>Program&lt;/code> 及 &lt;code>Main&lt;/code> 方法，也不用再写很多常见的 &lt;code>using&lt;/code> 语句了。然后，在那段时间，我们还得到了什么呢？我们得到了 ASP.NET Core Minimal APIs。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">builder&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">WebApplication&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CreateBuilder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">app&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">builder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Build&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">MapGet&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="s">&amp;#34;Hello World!&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Run&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这些代码就很大程度上借助了顶级语句和隐式引入的特性。它们使得我们可以更简洁地编写 ASP.NET Core 应用程序。C# 用这层简洁的伪装，让更多的人认为用它开发 Web 应用程序是件很简单的事情（笑）。&lt;/p>
&lt;p>另外，从此，C# 也可以用 1 行代码实现 Hello World 了。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Hello World!&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="接口的抽象静态成员">
接口的抽象静态成员
&lt;a href="#%e6%8e%a5%e5%8f%a3%e7%9a%84%e6%8a%bd%e8%b1%a1%e9%9d%99%e6%80%81%e6%88%90%e5%91%98" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在以前的 C# 版本中，接口只能包含方法、属性、事件和索引器等成员，而不能包含静态成员。相信这是绝大多数 .NET 开发者的共识，也是绝大多数开发者在入门时的认知，以及在其他编程语言中的经验。&lt;/p>
&lt;p>然而，在 C# 11 中，我们可以在接口中声明抽象静态成员了：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">interface&lt;/span> &lt;span class="nc">IShape&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">double&lt;/span> &lt;span class="n">Area&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">static&lt;/span> &lt;span class="kd">abstract&lt;/span> &lt;span class="n">IShape&lt;/span> &lt;span class="n">Create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">double&lt;/span> &lt;span class="n">size&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Circle&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">IShape&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">double&lt;/span> &lt;span class="n">Radius&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">double&lt;/span> &lt;span class="n">Area&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">PI&lt;/span> &lt;span class="p">*&lt;/span> &lt;span class="n">Radius&lt;/span> &lt;span class="p">*&lt;/span> &lt;span class="n">Radius&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="n">Circle&lt;/span> &lt;span class="n">Create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">double&lt;/span> &lt;span class="n">size&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Circle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样的声明会要求实现该接口的类必须提供这个静态方法。那么这个新特性有什么用呢？&lt;/p>
&lt;p>很快，我们在 C# 12 中就看到了它的应用：&lt;code>INumber&lt;/code> 接口的引入。这个接口定义了数字类型的通用行为，其中就包含了不少静态的成员，比如 &lt;code>Parse&lt;/code>、&lt;code>Zero&lt;/code> 等等。&lt;/p>
&lt;p>不难想象，如果没有这个新的接口特性，这个接口的实现肯定是做不到的。&lt;/p>
&lt;h2 id="结论">
结论
&lt;a href="#%e7%bb%93%e8%ae%ba" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>其实其他类似的例子还有很多，比如 &lt;code>ref struct&lt;/code>、&lt;code>readonly struct&lt;/code> 等与 &lt;code>Span&lt;/code> 及 &lt;code>Memory&lt;/code> 相关的特性，&lt;code>IAsyncEnumerable&lt;/code> 与 &lt;code>Channel&lt;/code> 等等。&lt;/p>
&lt;p>通过这些例子，我们可以看到，.NET 的新特性往往是为了支持某个特定的功能或语法而引入的。它们之间有着密切的关系。有的是为了支持某个功能的实现，有的是为了优化某个语法的使用体验，有的则是为了提供更好的性能或可读性。&lt;/p>
&lt;p>至于为什么有的特性并不是完全在同一个版本出现，这也是有一些原因的。其中一个原因是，.NET 的新特性往往需要经过多次迭代和完善才能最终稳定下来。有可能直到某个版本要发布时，想要添加的新特性仍然不够成熟，因此为了辅助它而诞生的特性可能会提前在该版本上线，而它所辅助的特性则会在下一个版本中引入。&lt;/p>
&lt;p>希望通过这篇文章，大家能够对 .NET 的新特性有一个更深入的理解。逆向思考这些特性背后的逻辑和思考方式，可以帮助我们更好地理解它们的设计初衷和应用场景，也能让我们在使用这些特性时更加得心应手。这样的例子见得多了，可能会有一种“微软在开发某功能时引入的新特性因为太好用了，所以顺便下放给我们使用”的感觉吧😂。&lt;/p></description></item><item><title>是不是所有 C# 中实现了 Dispose 方法的类我们都要用完即释放？</title><link>https://blog.coldwind.top/posts/no-need-to-always-call-dispose/</link><pubDate>Mon, 21 Apr 2025 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/no-need-to-always-call-dispose/</guid><description>&lt;p>C# 作为一个有 GC（垃圾回收）的语言，在使用托管资源时，通常不需要开发者关注资源的释放问题。但如果使用了非托管资源（常见的如文件句柄、数据库连接等），就需要手动释放资源了。为了方便管理资源，并形成一种统一的规范，C# 提供了 &lt;code>IDisposable&lt;/code> 接口（后来还提供了 &lt;code>IAsyncDisposable&lt;/code> 接口），开发者可以通过这个接口提供的 &lt;code>Dispose&lt;/code> 方法来释放资源，还可以借助 C# 的 &lt;code>using&lt;/code> 语句来简化资源的释放过程。&lt;/p>
&lt;p>那么问题来了：是否所有实现了 &lt;code>IDisposable&lt;/code> 接口的类都需要在用后立刻调用 &lt;code>Dispose&lt;/code> 方法？答案是：不一定。&lt;/p>
&lt;p>这篇文章我们就借助几个典型的例子，来看看在什么情况下可以不调用 &lt;code>Dispose&lt;/code> 方法，并从底层的原理出发，给大家提供一个判断是否有必要调用 &lt;code>Dispose&lt;/code> 方法的思路。&lt;/p>
&lt;h2 id="实现-idisposable-接口但不涉及资源释放的类">
实现 IDisposable 接口但不涉及资源释放的类
&lt;a href="#%e5%ae%9e%e7%8e%b0-idisposable-%e6%8e%a5%e5%8f%a3%e4%bd%86%e4%b8%8d%e6%b6%89%e5%8f%8a%e8%b5%84%e6%ba%90%e9%87%8a%e6%94%be%e7%9a%84%e7%b1%bb" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>首先我们看第一种情况：有些类实现了 &lt;code>IDisposable&lt;/code> 接口，但并不涉及资源的释放。这时候相信有的读者就会问了：这种情况有点太强行凑数了吧？而且这难道不是在滥用 &lt;code>IDisposable&lt;/code> 吗，毕竟它本来是用来释放资源的啊？&lt;/p>
&lt;p>其实未必。因为 C# 的 &lt;code>using&lt;/code> 关键字提供了一个非常方便的语法糖，可以让我们在使用完一个对象后，自动调用它的 &lt;code>Dispose&lt;/code> 方法中的逻辑（即便它可能与资源释放无关）。这样我们就可以实现延迟执行，以及在任何情况下（包括抛异常）都能够确保会执行的逻辑了。&lt;/p>
&lt;p>首先我们简单回顾一下 &lt;code>using&lt;/code> 关键字在幕后做的事情。我们这里看一个简单例子：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">fs&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">FileStream&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;test.txt&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">FileMode&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Open&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 其他逻辑&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>上面的代码在编译后会变成下面的代码：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">fs&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">FileStream&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;test.txt&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">FileMode&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Open&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 其他逻辑&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">finally&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">fs&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="n">IDisposable&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">fs&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">Dispose&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">在上面的例子中，&lt;code>using&lt;/code> 关键字并没有搭配花括号进行使用。这是 C# 8.0 中新增的语法糖，可以让我们减少一层缩进。它相当于花括号涵盖了从 &lt;code>using&lt;/code> 关键字到作用域的结束这个范围。&lt;/div>
&lt;/div>
&lt;p>我们可以看到，&lt;code>using&lt;/code> 语句在编译后会变成一个 &lt;code>try...finally&lt;/code> 语句块，确保了在 &lt;code>try&lt;/code> 块中的代码执行完后，无论是否出现异常，最终都会执行 &lt;code>finally&lt;/code> 块中的代码。&lt;/p>
&lt;p>于是我们就可以借助这个语法来实现一些延迟执行的逻辑了，尤其是类似 Go 语言中的 &lt;code>defer&lt;/code> 语句。Go 语言中，&lt;code>defer&lt;/code> 语句会在函数返回时执行，比如下面这个例子：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;defer&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;hello&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>于是我们可以仿照这个思路，在 C# 中实现一个类似的功能：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Defer&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">IDisposable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">Action&lt;/span> &lt;span class="n">_action&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">Defer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Action&lt;/span> &lt;span class="n">action&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_action&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">action&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Dispose&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_action&lt;/span>&lt;span class="p">?.&lt;/span>&lt;span class="n">Invoke&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>它在构建时会传入一个 &lt;code>Action&lt;/code> 委托，表示需要延迟执行的逻辑。然后我们就可以像下面这样使用它了：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">defer&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Defer&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;defer&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;hello&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>所以对于这样的一个类，即便它实现了 &lt;code>IDisposable&lt;/code> 接口，我们也不必须在使用完后，手动调用它的 &lt;code>Dispose&lt;/code> 方法。因为它的 &lt;code>Dispose&lt;/code> 方法并不涉及资源的释放，而只是执行一些延迟逻辑而已。&lt;/p>
&lt;h2 id="因为基类或接口的约束而实现-idisposable-接口的类">
因为基类或接口的约束而实现 IDisposable 接口的类
&lt;a href="#%e5%9b%a0%e4%b8%ba%e5%9f%ba%e7%b1%bb%e6%88%96%e6%8e%a5%e5%8f%a3%e7%9a%84%e7%ba%a6%e6%9d%9f%e8%80%8c%e5%ae%9e%e7%8e%b0-idisposable-%e6%8e%a5%e5%8f%a3%e7%9a%84%e7%b1%bb" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>除了上面提到的为了借助 &lt;code>using&lt;/code> 语句来实现延迟执行的逻辑外，还有一些类实现了 &lt;code>IDisposable&lt;/code> 接口，但并不涉及资源的释放。&lt;/p>
&lt;p>我们都知道，C# 中有一些原生的数据流，比如 &lt;code>FileStream&lt;/code>、&lt;code>MemoryStream&lt;/code>、&lt;code>GZipStream&lt;/code> 等等。它们的基类 &lt;code>Stream&lt;/code> 实现了 &lt;code>IDisposable&lt;/code> 接口，并且它们根据自己的实际情况，也各自提供了具体的 &lt;code>Dispose&lt;/code> 方法的实现，比如 &lt;code>FileStream&lt;/code> 会关闭文件句柄，从而释放文件资源，避免文件被占用。&lt;/p>
&lt;p>但这其中的 &lt;code>MemoryStream&lt;/code> 就有些非同寻常了。它虽然是一个数据流，但它并不涉及资源的释放。因为它的底层数据是存储在内存中的一个字节数组（&lt;code>byte[]&lt;/code>）中，而这个字节数组是一个托管资源。在它的源代码中我们可以看到：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MemoryStream&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">Stream&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">_buffer&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// Either allocated internally or externally.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">_origin&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// For user-provided arrays, start at this origin&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">_position&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// read/write head.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">_length&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// Number of bytes within the memory stream&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">_capacity&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// length of usable portion of buffer for stream&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">_expandable&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// User-provided buffers aren&amp;#39;t expandable.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">_writable&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// Can user write to this stream?&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">_exposable&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// Whether the array can be returned to the user.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">_isOpen&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// Is this stream open or closed?&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">protected&lt;/span> &lt;span class="kd">override&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Dispose&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">bool&lt;/span> &lt;span class="n">disposing&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">disposing&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_isOpen&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_writable&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_expandable&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Don&amp;#39;t set buffer to null - allow TryGetBuffer, GetBuffer &amp;amp; ToArray to work.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_lastReadTask&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">default&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>所以对于 &lt;code>MemoryStream&lt;/code> 这样的 &lt;code>Dispose&lt;/code> 方法并不涉及资源释放的类型，即便我们不调用它的 &lt;code>Dispose&lt;/code> 方法，也不会造成资源泄漏。当然了，这并不意味着我们就不需要甚至不应该去做这件事情，因为规范的开发习惯仍旧是可以为我们的代码带来更好的可读性和可维护性的。&lt;/p>
&lt;h2 id="在特定情况下可以不调用-dispose-方法的类">
在特定情况下可以不调用 Dispose 方法的类
&lt;a href="#%e5%9c%a8%e7%89%b9%e5%ae%9a%e6%83%85%e5%86%b5%e4%b8%8b%e5%8f%af%e4%bb%a5%e4%b8%8d%e8%b0%83%e7%94%a8-dispose-%e6%96%b9%e6%b3%95%e7%9a%84%e7%b1%bb" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>有些类提供了 &lt;code>Dispose&lt;/code> 方法，但是在某些情况下，对这一方法的调用并不是至关重要的。这里有一个典型的例子就是我们在异步编程中常见的 &lt;code>CancellationTokenSource&lt;/code>（下面简称为 CTS）。&lt;/p>
&lt;p>首先我们来写一个简单的代码，并观察它在运行时的内存占用：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="m">100000000&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">cts&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">CancellationTokenSource&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>运行后我们会发现，内存几乎没有任何变化。这是否意味着，CTS 的 &lt;code>Dispose&lt;/code> 方法并不重要呢？其实并不是，但是在上面的这个用法中，调用与否确实没有太大的区别。这是怎么回事呢？&lt;/p>
&lt;p>我们来观察 CTS 的 &lt;code>Dispose&lt;/code> 方法的实现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">protected&lt;/span> &lt;span class="k">virtual&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Dispose&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">bool&lt;/span> &lt;span class="n">disposing&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">disposing&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="n">_disposed&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ITimer&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">timer&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">_timer&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">timer&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_timer&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">timer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Dispose&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_registrations&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">_kernelEvent&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ManualResetEvent&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">mre&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Interlocked&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Exchange&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">ManualResetEvent&lt;/span>&lt;span class="p">?&amp;gt;(&lt;/span>&lt;span class="k">ref&lt;/span> &lt;span class="n">_kernelEvent&lt;/span>&lt;span class="p">!,&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">mre&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">_state&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="n">States&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">NotifyingState&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">mre&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Dispose&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_disposed&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>可以发现，这里它对两个“可有可无”的对象进行了回收，分别是：&lt;/p>
&lt;ul>
&lt;li>&lt;code>_timer&lt;/code>：一个 &lt;code>ITimer&lt;/code> 对象，表示一个定时器&lt;/li>
&lt;li>&lt;code>_kernelEvent&lt;/code>：一个 &lt;code>ManualResetEvent&lt;/code> 对象，是一个信号量&lt;/li>
&lt;/ul>
&lt;p>它们分别是做什么用的呢？首先我们来看定时器。我们知道，CTS 提供了延时自动取消的功能。比如我们希望在 5 秒后自动取消，那么实现方法可以是：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">delay&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">TimeSpan&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FromSeconds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">cts&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">CancellationTokenSource&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">delay&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 或者也可以在创建后调用 CancelAfter 方法&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>此时，CTS 内部就会创建这个定时器，从而实现这个功能。在这一情况下，就会产生需要我们去释放的资源了。&lt;/p>
&lt;p>另外一个信号量又是怎么回事呢？&lt;/p>
&lt;p>我们都知道，CTS 现在常用于异步编程。它的 &lt;code>CancellationToken&lt;/code> 可以传给标准库提供的 &lt;code>Async&lt;/code> 结尾的方法，从而实现任务的取消。但是一些老的库函数可能并不支持 &lt;code>CancellationToken&lt;/code>，这时候我们就可以借助 &lt;code>token&lt;/code> 上的这个 &lt;code>WaitHandle&lt;/code> 来实现任务的取消了。下面是 CTS 中关于 &lt;code>WaitHandle&lt;/code> 属性的实现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">internal&lt;/span> &lt;span class="n">WaitHandle&lt;/span> &lt;span class="n">WaitHandle&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">get&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ThrowIfDisposed&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Return the handle if it was already allocated.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">_kernelEvent&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_kernelEvent&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Lazily-initialize the handle.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">mre&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ManualResetEvent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">Interlocked&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CompareExchange&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">ref&lt;/span> &lt;span class="n">_kernelEvent&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mre&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">mre&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Dispose&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">IsCancellationRequested&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_kernelEvent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Set&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_kernelEvent&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>可以发现，它默认是没有值的；当我们第一次访问它时，它便会创建一个新的，并返回它。这样的操作就会产生一个需要我们去释放资源的对象。我们可以做这样的一个实验：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="m">100000000&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">cts&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">CancellationTokenSource&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">handle&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">cts&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Token&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WaitHandle&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">&lt;code>WaitHandle&lt;/code> 在 CTS 上是 &lt;code>internal&lt;/code> 的，我们只能也应当在 &lt;code>CancellationToken&lt;/code> 上去访问，因为通常情况下，我们传给方法的参数并不是 CTS 对象本身，而是它的 &lt;code>Token&lt;/code>。&lt;/div>
&lt;/div>
&lt;p>然后运行程序，就会发现内存在不断增加。只要我们调用了 &lt;code>CTS&lt;/code> 的 &lt;code>Dispose&lt;/code> 方法，内存便会不再上升。&lt;/p>
&lt;p>所以我们可以得出结论：如果我们在使用 CTS 时，既不使用延时自动取消的功能，也不使用 &lt;code>WaitHandle&lt;/code> 属性，那么我们不调用 &lt;code>Dispose&lt;/code> 方法也不会造成资源的泄漏。&lt;/p>
&lt;h2 id="虽然提供了-dispose-方法但不应该用完立即释放的类">
虽然提供了 Dispose 方法，但不应该用完立即释放的类
&lt;a href="#%e8%99%bd%e7%84%b6%e6%8f%90%e4%be%9b%e4%ba%86-dispose-%e6%96%b9%e6%b3%95%e4%bd%86%e4%b8%8d%e5%ba%94%e8%af%a5%e7%94%a8%e5%ae%8c%e7%ab%8b%e5%8d%b3%e9%87%8a%e6%94%be%e7%9a%84%e7%b1%bb" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>还有一种情况是，虽然类实现了 &lt;code>IDisposable&lt;/code> 接口，但我们并不应该在用完后立即释放它。它被设计出来就是希望我们能够复用的。典型的例子就是 &lt;code>HttpClient&lt;/code>。&lt;/p>
&lt;p>比如下面这个错误例子：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">urls&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;https://www.baidu.com&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;https://www.sogou.com&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;https://www.sohu.com&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">url&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">urls&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">client&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">HttpClient&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">client&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这个例子就是一个错误的用法。正确的做法应该是将 &lt;code>HttpClient&lt;/code> 的声明移动到循环外部，或者还可以声明为一个静态对象等。这是为什么呢？&lt;/p>
&lt;p>简单来说，&lt;code>HttpClient&lt;/code> 底层会使用 &lt;code>HttpClientHandler&lt;/code> 去处理涉及到连接池、Socket、TCP 连接等资源的管理。TCP 连接因为比较昂贵（比如有三次握手、四次挥手等），所以它通常会被复用。当我们使用 &lt;code>HttpClient&lt;/code> 去访问一个链接时，访问结束后这个 TCP 连接并不会立即关闭，而是会被放入连接池中，等待下次的复用。&lt;/p>
&lt;p>但是，如果我们在每次请求时都创建一个新的 &lt;code>HttpClient&lt;/code> 对象，那么这个 TCP 连接就会占据连接池中的一个位置，还有本地端口等资源，最终可能会导致连接池或本地端口的耗尽，进而抛出 &lt;code>SocketException&lt;/code> 等异常。&lt;/p>
&lt;p>因为 &lt;code>HttpClient&lt;/code> 是一个包装好的功能相当灵活的类，因此我们完全可以只创建一个，并且多次使用。不管我们访问的链接是否可以复用，怎么复用，保持连接状态多久，都会被它妥善处理。所以对于一个本地项目（如控制台应用、WPF 应用等），我们完全可以创建一个单例并处处使用它。&lt;/p>
&lt;p>如果我们在项目中还使用了 DI 容器（比如微软官方提供的 &lt;code>Microsoft.Extensions.DependencyInjection&lt;/code>），那么我们可以将 &lt;code>HttpClient&lt;/code> 注册为一个单例的服务。这样我们就可以在整个项目中复用它了。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">services&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ServiceCollection&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">services&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AddSingleton&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">HttpClient&lt;/span>&lt;span class="p">&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">serviceProvider&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">services&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">BuildServiceProvider&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">client&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">serviceProvider&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetService&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">HttpClient&lt;/span>&lt;span class="p">&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>当然了，这个方式并不是最推荐的方式（不过对于简单的本地项目来说是可取的）。对于复杂些的项目，我们更应该考虑的方式是使用 &lt;code>HttpClientFactory&lt;/code>。要使用它，我们还要引入一个包：&lt;code>Microsoft.Extentions.Http&lt;/code>（控制台或 WPF 等本地程序通常需要，而 &lt;code>ASP.NET Core&lt;/code> 项目会自动引入它，因此就不需要额外安装包了），然后就可以使用了：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">services&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ServiceCollection&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">services&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AddHttpClient&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">serviceProvider&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">services&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">BuildServiceProvider&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">factory&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">serviceProvider&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetService&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">IHttpClientFactory&lt;/span>&lt;span class="p">&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">client&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">factory&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CreateClient&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>对于更加复杂的情况，比如我们希望为不同的服务类注入配置不同的 &lt;code>HttpClient&lt;/code>（比如不同的 BaseAddress、Header、Timeout 等），我们可以使用 &lt;code>AddHttpClient&lt;/code> 的“命名客户端”的方法来实现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">services&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ServiceCollection&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">services&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AddHttpClient&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;baidu&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">c&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">BaseAddress&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Uri&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;https://www.baidu.com&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Timeout&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">TimeSpan&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FromSeconds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">30&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">services&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AddHttpClient&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;sogou&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">c&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">BaseAddress&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Uri&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;https://www.sogou.com&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">c&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">DefaultRequestHeaders&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;User-Agent&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">serviceProvider&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">services&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">BuildServiceProvider&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">factory&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">serviceProvider&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetService&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">IHttpClientFactory&lt;/span>&lt;span class="p">&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">baiduClient&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">factory&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CreateClient&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;baidu&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">sogouClient&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">factory&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CreateClient&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;sogou&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样我们就可以为不同的服务类注入不同的 &lt;code>HttpClient&lt;/code> 了。关于 &lt;code>HttpClientFactory&lt;/code> 的更多用法，可以参考&lt;a class="link" href="https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/http-requests?view=aspnetcore-9.0" target="_blank" rel="noopener"
>官方的教程&lt;/a>。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在这篇文章中，我们讨论了在 C# 中是否需要总是调用 &lt;code>Dispose&lt;/code> 方法的问题。我们通过几个典型的例子，来看看在什么情况下可以不调用 &lt;code>Dispose&lt;/code> 方法，并从底层的原理出发，给大家提供一个判断是否有必要调用 &lt;code>Dispose&lt;/code> 方法的思路。&lt;/p>
&lt;p>在实际的开发中，与其说我们需要分辨出哪些对象是可以不用释放的，不如说我们应当明白如何对这一操作的必要性进行正确的判断，并养成统一且规范的开发习惯。这样不管是对团队的其他开发者，还是未来的自己，都是有好处的。&lt;/p></description></item><item><title>在多线程开发中用信号量代替轮询和标志位</title><link>https://blog.coldwind.top/posts/use-signal-over-polling-flags/</link><pubDate>Wed, 02 Apr 2025 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/use-signal-over-polling-flags/</guid><description>&lt;blockquote>
&lt;p>本文有对应的视频教程：&lt;a class="link" href="https://www.bilibili.com/video/BV1e7o2YTEpi" target="_blank" rel="noopener"
>哔哩哔哩&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>我们在多线程开发中，经常会用到标志位和轮询，从而控制一个线程中的执行逻辑。但是这样的方式会导致代码的可读性和可维护性下降，并且也不够优雅。这篇文章我们来看一看如何用信号量等机制来替代轮询标志位的方式，从而实现线程间的通信和控制。&lt;/p>
&lt;h2 id="传统方式">
传统方式
&lt;a href="#%e4%bc%a0%e7%bb%9f%e6%96%b9%e5%bc%8f" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>首先我们来看一看传统的标志位和轮询是怎么一回事。这里我们用一个简单的例子来探讨：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyService&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kd">volatile&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">_shouldStop&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="n">Thread&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">_workerThread&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_workerThread&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Thread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Worker&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_shouldStop&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_workerThread&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Start&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Stop&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_shouldStop&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_workerThread&lt;/span>&lt;span class="p">?.&lt;/span>&lt;span class="n">Join&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Worker&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(!&lt;/span>&lt;span class="n">_shouldStop&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 执行一些工作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Thread&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">50&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 模拟工作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>上面就是一个典型的例子。这里，&lt;code>_shouldStop&lt;/code> 是一个标志位，表示线程是否需要停止。我们在 &lt;code>Start&lt;/code> 和 &lt;code>Stop&lt;/code> 方法中分别设置和读取这个标志位，并在 &lt;code>Worker&lt;/code> 方法中，通过轮询它来判断线程是否需要继续执行。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">上面的标志位 &lt;code>_shouldStop&lt;/code> 是 &lt;code>volatile&lt;/code> 的，这样做能够保证编译器不会对其进行优化，从而保证每次读取都是最新的值。其实一般情况下，如果我们的轮询中包含了 &lt;code>Thread.Sleep&lt;/code> 等操作，那么即便不加 &lt;code>volatile&lt;/code>，也依旧是可以读到最新的值的。&lt;/div>
&lt;/div>
&lt;div class="notice warning">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-triangle" aria-hidden="true">&lt;/i>Warning
&lt;/div>
&lt;div class="notice-content">注意，这里我们只是用简单的代码大概介绍思路，并没有提供一个稳健的实现。比如上面的例子中，我们并没有处理用户多次调用 &lt;code>Start&lt;/code> 方法，也没有处理线程异常等情况。&lt;/div>
&lt;/div>
&lt;p>如果我们想在上面的基础上再添加暂停和继续的功能，那么我们就需要添加更多的标志位和轮询逻辑。比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyService&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kd">volatile&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">_shouldStop&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kd">volatile&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">_isRunning&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="n">Thread&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">_workerThread&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_workerThread&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Thread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Worker&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_shouldStop&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_isRunning&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_workerThread&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Start&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Stop&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_shouldStop&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_workerThread&lt;/span>&lt;span class="p">?.&lt;/span>&lt;span class="n">Join&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Pause&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_isRunning&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Resume&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_isRunning&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Worker&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(!&lt;/span>&lt;span class="n">_shouldStop&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">_isRunning&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 执行一些工作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Thread&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">50&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 暂停时也要休眠，避免 CPU 占用过高&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="经典的两个标志位">
经典的两个标志位
&lt;a href="#%e7%bb%8f%e5%85%b8%e7%9a%84%e4%b8%a4%e4%b8%aa%e6%a0%87%e5%bf%97%e4%bd%8d" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>现在我们来看一看如何使用信号量来替代标志位。&lt;/p>
&lt;h3 id="用-manualresetevent-实现线程的暂停和继续">
用 &lt;code>ManualResetEvent&lt;/code> 实现线程的暂停和继续
&lt;a href="#%e7%94%a8-manualresetevent-%e5%ae%9e%e7%8e%b0%e7%ba%bf%e7%a8%8b%e7%9a%84%e6%9a%82%e5%81%9c%e5%92%8c%e7%bb%a7%e7%bb%ad" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>我们先来思考一下，上面的 &lt;code>_isRunning&lt;/code> 的作用和效果是什么。我们想用它的值来控制线程是否要执行操作，但是我们不能在它发生变化时立刻得到通知，因此我们只能每隔一段时间去轮询一下。那么，如果有办法能够在它为 &lt;code>false&lt;/code> 时不需要我们轮询，而是直接阻塞在某个地方，等到它变为 &lt;code>true&lt;/code> 时再继续执行，是不是就好很多了？&lt;/p>
&lt;p>根据这一需求，我们可以使用 &lt;code>WaitHandle&lt;/code> 的两个子类——&lt;code>ManualResetEvent&lt;/code> 及 &lt;code>AutoResetEvent&lt;/code> 来实现。&lt;code>ManualResetEvent&lt;/code> 是一个可以手动重置的信号量。当它 &lt;code>Set&lt;/code> 后，将会保持放行状态，直到再次 &lt;code>Reset&lt;/code> 才会关闭。与它相对的是 &lt;code>AutoResetEvent&lt;/code>，它会在每次放行后自动重置。这里更符合我们的需求的是 &lt;code>ManualResetEvent&lt;/code>，因为我们希望放行后能够连续执行多次，而不需要每次都 &lt;code>Set&lt;/code> 后执行一次。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyService&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kd">volatile&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">_shouldStop&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="n">ManualResetEvent&lt;/span> &lt;span class="n">_isRunningEvent&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ManualResetEvent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 初始是关闭的&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="n">Thread&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">_workerThread&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_workerThread&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Thread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Worker&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_shouldStop&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_isRunningEvent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Set&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 线程开始时放行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_workerThread&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Start&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Stop&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_shouldStop&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_workerThread&lt;/span>&lt;span class="p">?.&lt;/span>&lt;span class="n">Join&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Pause&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_isRunningEvent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Reset&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 关闭信号量&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Resume&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_isRunningEvent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Set&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 放行信号量&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Worker&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(!&lt;/span>&lt;span class="n">_shouldStop&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_isRunningEvent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WaitOne&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 等待信号量放行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 执行一些工作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Thread&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">50&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 适当休眠，避免 CPU 占用过高&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="用-cancellationtoken-实现任务的停止">
用 &lt;code>CancellationToken&lt;/code> 实现任务的停止
&lt;a href="#%e7%94%a8-cancellationtoken-%e5%ae%9e%e7%8e%b0%e4%bb%bb%e5%8a%a1%e7%9a%84%e5%81%9c%e6%ad%a2" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>我们可以进一步优化上面的例子，比如我们可以使用 &lt;code>CancellationToken&lt;/code> 来实现任务的停止。&lt;code>CancellationToken&lt;/code> 是 .NET 中用于取消操作的机制，它可以在任务中传递一个取消请求，并且可以在任务中检查这个请求。它不仅可以用于异步编程，也可以用于多线程编程。这里，我们用它来取代 &lt;code>_shouldStop&lt;/code> 标志位。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyService&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="n">ManualResetEvent&lt;/span> &lt;span class="n">_isRunningEvent&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ManualResetEvent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 初始是关闭的&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="n">Thread&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">_workerThread&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="n">CancellationTokenSource&lt;/span> &lt;span class="n">_cancellationTokenSource&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">CancellationTokenSource&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_workerThread&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Thread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Worker&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_isRunningEvent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Set&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 线程开始时放行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_workerThread&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Start&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Stop&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_cancellationTokenSource&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Cancel&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 取消操作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_workerThread&lt;/span>&lt;span class="p">?.&lt;/span>&lt;span class="n">Join&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Pause&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_isRunningEvent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Reset&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 关闭信号量&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Resume&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_isRunningEvent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Set&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 放行信号量&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Worker&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(!&lt;/span>&lt;span class="n">_cancellationTokenSource&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Token&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IsCancellationRequested&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_isRunningEvent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WaitOne&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 等待信号量放行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 执行一些工作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Thread&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">50&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 适当休眠，避免 CPU 占用过高&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>上面的例子因为比较简单，所以并没有体现出使用 &lt;code>CancellationToken&lt;/code> 的优势。实际上，有很多方法都可以接收一个 &lt;code>CancellationToken&lt;/code> 参数。这样我们还可以通过传递它来实现停止在 &lt;code>Worker&lt;/code> 方法中调用的长时间运行的其他任务；否则我们可能就只能在取消后等待这些任务的结束了。&lt;/p>
&lt;h2 id="优化使用消息队列的情形">
优化使用消息队列的情形
&lt;a href="#%e4%bc%98%e5%8c%96%e4%bd%bf%e7%94%a8%e6%b6%88%e6%81%af%e9%98%9f%e5%88%97%e7%9a%84%e6%83%85%e5%bd%a2" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>除了上面的例子，我们还经常会遇到需要使用一个队列来实现生产者消费者模式的情况。比如下面这个例子：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyService&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">Queue&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_queue&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Queue&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">_lock&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="kt">object&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kd">volatile&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">_shouldStop&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kd">volatile&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">_isRunning&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="n">Thread&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">_workerThread&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_workerThread&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Thread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Worker&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_shouldStop&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_isRunning&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_workerThread&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Start&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Stop&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_shouldStop&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_workerThread&lt;/span>&lt;span class="p">?.&lt;/span>&lt;span class="n">Join&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 省略 Pause 和 Resume 方法的实现&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Enqueue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">item&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">lock&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">_lock&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_queue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Enqueue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Worker&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(!&lt;/span>&lt;span class="n">_shouldStop&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">lock&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">_lock&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">_queue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Count&lt;/span> &lt;span class="p">&amp;gt;&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">_isRunning&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 也可以使用 TryDequeue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">_queue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Dequeue&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 处理 item&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Thread&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">50&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 暂停时也要休眠，避免 CPU 占用过高&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在上面的例子中，我们具体做了这样几件事情：&lt;/p>
&lt;ol>
&lt;li>使用 &lt;code>Queue&lt;/code> 来存储数据，并使用线程锁和 &lt;code>lock&lt;/code> 语句来保证线程安全；&lt;/li>
&lt;li>使用 &lt;code>_shouldStop&lt;/code> 和 &lt;code>_isRunning&lt;/code> 来控制线程的执行；&lt;/li>
&lt;li>在 &lt;code>Worker&lt;/code> 方法中使用 &lt;code>lock&lt;/code> 来获取锁，并在队列不为空时获取传入的任务和进行处理；&lt;/li>
&lt;li>暴露一个 &lt;code>Enqueue&lt;/code> 方法来让生产者添加任务到队列中。&lt;/li>
&lt;/ol>
&lt;p>那么我们该如何优化这个例子呢？&lt;/p>
&lt;h3 id="用线程安全的集合类型">
用线程安全的集合类型
&lt;a href="#%e7%94%a8%e7%ba%bf%e7%a8%8b%e5%ae%89%e5%85%a8%e7%9a%84%e9%9b%86%e5%90%88%e7%b1%bb%e5%9e%8b" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>实际上，.NET 标准库中已经提供了线程安全的集合类型，比如 &lt;code>ConcurrentQueue&amp;lt;T&amp;gt;&lt;/code>。它们可以在多线程环境中安全地使用，而不需要我们手动加锁。我们可以直接用它来替代上面的 &lt;code>Queue&lt;/code> 和 &lt;code>lock&lt;/code> 语句。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyService&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">ConcurrentQueue&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_queue&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ConcurrentQueue&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Enqueue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">item&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_queue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Enqueue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Worker&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(!&lt;/span>&lt;span class="n">_shouldStop&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">_isRunning&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">_queue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">TryDequeue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">out&lt;/span> &lt;span class="kt">var&lt;/span> &lt;span class="n">item&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1">// 也可以使用 TryDequeue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 处理 item&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Thread&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">50&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 暂停时也要休眠，避免 CPU 占用过高&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>通过这样的方式，我们就不需要手动加锁了。&lt;code>ConcurrentQueue&amp;lt;T&amp;gt;&lt;/code> 会自动处理线程安全的问题。&lt;/p>
&lt;h3 id="用信号量来取代标志位">
用信号量来取代标志位
&lt;a href="#%e7%94%a8%e4%bf%a1%e5%8f%b7%e9%87%8f%e6%9d%a5%e5%8f%96%e4%bb%a3%e6%a0%87%e5%bf%97%e4%bd%8d" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>上面的代码中，我们又用到了轮询。但是这个轮询本质上做的事情是等待队列中有数据可用。基于这一思路，我们可以考虑用一个只在有新数据到来时才放行一次的信号量——也就是 &lt;code>AutoResetEvent&lt;/code> 来替代它。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyService&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">ConcurrentQueue&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_queue&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ConcurrentQueue&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">AutoResetEvent&lt;/span> &lt;span class="n">_queueEvent&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">AutoResetEvent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 初始是关闭的&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Enqueue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">item&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_queue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Enqueue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_queueEvent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Set&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 放行信号量&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Worker&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(!&lt;/span>&lt;span class="n">_shouldStop&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_queueEvent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WaitOne&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 等待信号量放行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">_isRunning&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">_queue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">TryDequeue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">out&lt;/span> &lt;span class="kt">var&lt;/span> &lt;span class="n">item&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1">// 也可以使用 TryDequeue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 处理 item&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但是这个例子并不好，因为如果同时来了多条数据，那么我们虽然会调用多次 &lt;code>Set&lt;/code>，但是信号量只会放行一次。可就有可能出现数据处理不及时的情况。所以更好的方式是使用 &lt;code>Semaphore&lt;/code>。它好比一扇宽度可变的大门。每次放行都会让门变宽一些，而不像是 &lt;code>AutoResetEvent&lt;/code> 那样只有开和关这两种状态。不过这个例子我们就不演示了，因为我们有更好的方法。&lt;/p>
&lt;h3 id="用-blockingcollection-来实现生产者消费者模式">
用 &lt;code>BlockingCollection&lt;/code> 来实现生产者消费者模式
&lt;a href="#%e7%94%a8-blockingcollection-%e6%9d%a5%e5%ae%9e%e7%8e%b0%e7%94%9f%e4%ba%a7%e8%80%85%e6%b6%88%e8%b4%b9%e8%80%85%e6%a8%a1%e5%bc%8f" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>实际上，.NET 中已经提供了一个现成的类来实现生产者消费者模式——&lt;code>BlockingCollection&amp;lt;T&amp;gt;&lt;/code>。它是一个线程安全的集合类型，而且它还提供了阻塞和通知的功能。我们可以直接用它来替代上面的 &lt;code>ConcurrentQueue&amp;lt;T&amp;gt;&lt;/code> 和 &lt;code>AutoResetEvent&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyService&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">BlockingCollection&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_queue&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">BlockingCollection&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Enqueue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">item&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_queue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 添加数据到队列中&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Worker&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(!&lt;/span>&lt;span class="n">_shouldStop&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">_isRunning&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">_queue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">TryTake&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">out&lt;/span> &lt;span class="kt">var&lt;/span> &lt;span class="n">item&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Timeout&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Infinite&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1">// 等待数据可用&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 处理 item&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样，如果队列为空时，&lt;code>TryTake&lt;/code> 会阻塞当前线程，直到有数据可用。当调用 &lt;code>Add&lt;/code> 方法时，&lt;code>BlockingCollection&amp;lt;T&amp;gt;&lt;/code> 会自动放行等待的线程。这样我们就不需要手动处理信号量了。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">其实通过观察 &lt;code>BlockingCollection&amp;lt;T&amp;gt;&lt;/code> 的&lt;a class="link" href="https://source.dot.net/#System.Collections.Concurrent/System/Collections/Concurrent/BlockingCollection.cs,3fc8b6e4e28ee36c" target="_blank" rel="noopener"
>源代码&lt;/a>，我们不难发现它在底层用到了 &lt;code>ConcurrentQueue&amp;lt;T&amp;gt;&lt;/code> 和 &lt;code>SemaphoreSlim&lt;/code>。此外，它底层使用的集合类型也是可变的，比如 &lt;code>ConcurrentStack&amp;lt;T&amp;gt;&lt;/code> 和 &lt;code>ConcurrentBag&amp;lt;T&amp;gt;&lt;/code> 等。我们可以通过传入不同的集合类型来实现不同的行为。&lt;/div>
&lt;/div>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在这篇文章中，我们探讨了如何用信号量等机制来替代轮询标志位的方式，从而实现线程间的通信和控制。我们使用了 &lt;code>ManualResetEvent&lt;/code>、&lt;code>AutoResetEvent&lt;/code>、&lt;code>CancellationToken&lt;/code> 和 &lt;code>BlockingCollection&amp;lt;T&amp;gt;&lt;/code> 等类来实现这些功能。通过这些类，我们可以更优雅地实现多线程编程，避免了轮询和标志位带来的问题。&lt;/p>
&lt;p>大家在实际的开发中，也一定要多多关注这些现成的类和工具，而不是盲目地自己造轮子。&lt;/p></description></item><item><title>BSON 与 MessagePack 的异同及如何选择</title><link>https://blog.coldwind.top/posts/bson-vs-msgpack/</link><pubDate>Thu, 20 Mar 2025 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/bson-vs-msgpack/</guid><description>&lt;img src="https://s2.loli.net/2025/03/21/mU1W2s4uAMnov78.webp" alt="Featured image of post BSON 与 MessagePack 的异同及如何选择" />&lt;p>BSON 与 MessagePack 是两种常见的二进制数据格式。他们都提供了序列化与反序列化功能，支持灵活的数据格式，也广泛地被各种编程语言所支持。但是它们之间有什么异同呢？它们的性能以及空间利用率如何呢？在实际应用中，我们又该如何选择呢？&lt;/p>
&lt;h2 id="基本信息">
基本信息
&lt;a href="#%e5%9f%ba%e6%9c%ac%e4%bf%a1%e6%81%af" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>BSON（Binary JSON）是一种二进制编码的 JSON 格式，由 MongoDB 开发，主要用于 MongoDB 数据库中数据的存储和传输。它扩展了 JSON 格式，增加了对额外数据类型的支持，如日期、时间戳和二进制数据。&lt;/p>
&lt;p>MessagePack 是一种高效的二进制序列化格式，旨在实现尽可能小的体积和尽可能快的处理速度。它支持多种编程语言，常用于网络通信和数据存储。&lt;/p>
&lt;h2 id="相同点">
相同点
&lt;a href="#%e7%9b%b8%e5%90%8c%e7%82%b9" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>BSON 和 MessagePack 在多个方面具有相似之处。首先，它们都将数据编码为二进制形式，与基于文本的 JSON 相比，可以减小数据体积，提高传输效率。JSON 虽然具有可读性的优势，但在处理大量数据时，其文本特性会导致解析速度较慢，且占用更多的存储空间。而二进制格式则避免了这些问题，它们以计算机更容易处理的方式存储数据，从而提高了效率。&lt;/p>
&lt;p>其次，它们都支持多种数据类型，包括基本类型（如整数、浮点数、字符串、布尔值）和复杂类型（如数组、二进制、日期等）。这使得它们可以灵活地处理各种数据结构，满足不同应用场景的需求。&lt;/p>
&lt;p>此外，两者都提供了多种编程语言的实现，使得它们可以在不同的系统和平台之间进行数据交换。无论是使用 Python、Java、C#、Go，还是其他编程语言，我们都可以找到相应的库来使用它们。最后，它们都提供了高效的序列化和反序列化机制，使得开发者可以方便地进行数据的相关操作。&lt;/p>
&lt;p>那么，同样都是二进制数据，它们是否都具备相当高的空间利用率以及性能呢？&lt;/p>
&lt;h2 id="不同点">
不同点
&lt;a href="#%e4%b8%8d%e5%90%8c%e7%82%b9" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>然而，BSON 和 MessagePack 之间也存在一些关键的设计上的差异。这些差异导致了它们的空间利用率，以及各自所擅长的功能并不相同。&lt;/p>
&lt;p>BSON 的设计目标是主要用于 MongoDB 的数据存储和传输，而不是为了提供一个 JSON 的上位替代。这种设计理念上的差异直接影响了它们在空间利用率和性能方面的表现。由于 BSON 包含一些额外的元数据（如字段长度等），因此其空间效率相对较低；而 MessagePack 使用紧凑的表示方式来编码数据，例如使用更少的字节来表示较小的整数，因此空间效率非常高。MessagePack 的设计哲学是“尽可能地小”，这使得它在对数据大小有严格要求的场景中成为理想的选择。&lt;/p>
&lt;p>在性能方面，BSON 的序列化和反序列化速度相对较慢，而 MessagePack 的序列化和反序列化速度非常快。这是因为 MessagePack 的编码方式更加简单高效，减少了处理数据所需的计算量。然而，BSON 虽然效率相对较低，但它在 MongoDB 中有许多优化，例如在索引方面，BSON 的结构允许 MongoDB 高效地遍历和查询数据，这弥补了其在通用性能上的一些不足。具体来说，MongoDB 可以利用 BSON 中存储的字段长度等信息，快速定位到需要的数据，而不需要像解析 JSON 那样逐个字符地扫描。&lt;/p>
&lt;h2 id="代码对比">
代码对比
&lt;a href="#%e4%bb%a3%e7%a0%81%e5%af%b9%e6%af%94" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>这里，我们在 C# 中用一个简单的例子来对比 BSON 和 MessagePack。我们使用 &lt;a class="link" href="https://www.nuget.org/packages/Newtonsoft.Json.Bson" target="_blank" rel="noopener"
>&lt;code>Newtonsoft.Json.Bson&lt;/code>&lt;/a> 和 &lt;a class="link" href="https://www.nuget.org/packages/MessagePack" target="_blank" rel="noopener"
>&lt;code>MessagePack&lt;/code>&lt;/a> 这两个库来实现。&lt;/p>
&lt;p>首先，我们设计一个包含一些属性的类，并且为它填充一些数据：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MyModel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Id&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">DateTime&lt;/span> &lt;span class="n">CreatedAt&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">IsActive&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">decimal&lt;/span> &lt;span class="n">Price&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">double&lt;/span> &lt;span class="n">Rating&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">Guid&lt;/span> &lt;span class="n">UniqueId&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">Data&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">Tags&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">Dictionary&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">Counts&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">BigNumber&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">short&lt;/span> &lt;span class="n">SmallNumber&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">char&lt;/span> &lt;span class="n">Initial&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">TimeSpan&lt;/span> &lt;span class="n">Duration&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">Uri&lt;/span> &lt;span class="n">Website&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">Version&lt;/span> &lt;span class="n">Version&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">DynamicValue&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">Status&lt;/span> &lt;span class="n">Status&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">enum&lt;/span> &lt;span class="n">Status&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Pending&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">InProgress&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Completed&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">model&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MyModel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Id&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">123&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;My Model&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">CreatedAt&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">DateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">UtcNow&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">IsActive&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Price&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">99.99&lt;/span>&lt;span class="n">m&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Rating&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">4.5&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">UniqueId&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Guid&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">NewGuid&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Data&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">3&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Tags&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s">&amp;#34;tag1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;tag2&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Counts&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Dictionary&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s">&amp;#34;a&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">1&lt;/span> &lt;span class="p">},&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s">&amp;#34;b&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">2&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">BigNumber&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">123456789012345&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">SmallNumber&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">123&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Initial&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="sc">&amp;#39;A&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Duration&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">TimeSpan&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FromMinutes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">15&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Website&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Uri&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;https://www.example.com&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Version&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Version&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">DynamicValue&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">Value&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;dynamic&amp;#34;&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Status&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Status&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">InProgress&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后，我们用两种方法来序列化这个对象。首先是 BSON：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">Newtonsoft.Json&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">Newtonsoft.Json.Bson&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">fs&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">File&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OpenWrite&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">@&amp;#34;bson.dat&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">writer&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">BsonWriter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fs&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">serializer&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">JsonSerializer&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">serializer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Serialize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">writer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">model&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后是 MessagePack。这里为了序列化上述模型，我们还需要为模型类添加一些特性，包括：&lt;/p>
&lt;ul>
&lt;li>为类添加 &lt;code>[MessagePackObject]&lt;/code>&lt;/li>
&lt;li>为每个属性依次添加 &lt;code>[Key(0)]&lt;/code>、&lt;code>[Key(1)]&lt;/code>、&lt;code>[Key(2)]&lt;/code>……&lt;/li>
&lt;/ul>
&lt;p>否则序列化代码将会报错。这里，修改后的模型类（部分）及序列化代码如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="na">[MessagePackObject]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MyModel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [Key(0)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Id&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [Key(1)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [Key(2)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">DateTime&lt;/span> &lt;span class="n">CreatedAt&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [Key(3)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">IsActive&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">fs&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">File&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OpenWrite&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">@&amp;#34;msgpack.dat&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">data&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">MessagePackSerializer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Serialize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">fs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后，我们观察文件大小：&lt;/p>
&lt;ul>
&lt;li>BSON：381 字节&lt;/li>
&lt;li>MsgPack：166 字节&lt;/li>
&lt;/ul>
&lt;p>可以明显看出，BSON 在空间效率上完全没有优势。&lt;/p>
&lt;div class="notice note">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-sticky-note" aria-hidden="true">&lt;/i>Note
&lt;/div>
&lt;div class="notice-content">实际上，就算是对比 JSON，BSON 在空间效率上也未必会有显著的优势。BSON 的优势主要来自对日期、二进制数据、数字等的处理，而 JSON 只能全部以字符串的形式存储。对于 100,000,000 这样的大数字，JSON 需要 9 个字符，而 BSON 只需要 4 个字节；但对于 1.0 这样的小数，JSON 需要 3 个字符，而 BSON 只需要 8 个字节。不仅如此，BSON 还包含了额外的元数据，如字段名称、字段长度等，这也导致了 BSON 的空间效率并没有相较于 JSON 有多少提升。&lt;/div>
&lt;/div>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>总而言之，BSON 和 MessagePack 都是优秀且现代的二进制序列化格式。BSON 的优势在于与 JSON 的兼容性和对丰富数据类型的支持，使得它在 MongoDB 等数据库应用中表现出色；MessagePack 的优势在于其高性能和高空间效率，这使得它在网络通信、大数据处理等需要快速传输和处理大量数据的场景中更有优势。在实际应用中，我们应当根据具体需求选择合适的格式。&lt;/p></description></item><item><title>为什么不试试 ReactiveUI 呢？</title><link>https://blog.coldwind.top/posts/why-not-using-rxui/</link><pubDate>Wed, 12 Mar 2025 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/why-not-using-rxui/</guid><description>&lt;p>在去年，我制作了&lt;a class="link" href="https://www.bilibili.com/video/BV1NA4m1w7rd/" target="_blank" rel="noopener"
>一期视频&lt;/a>与大家探讨响应式编程（Reactive Programming）。在视频的结尾，我也“剧透”了自己或许会在将来的一天继续深入这个话题，与大家探讨使用 ReactiveUI（以下简称为“RxUI”）这个库来做符合 MVVM 模式的桌面应用。&lt;/p>
&lt;p>但现在已经过去很久了，后续内容迟迟没有出现。这真的是一件尴尬的事情。事实上，这个话题可谓难度非常高，尤其是相较于其他 MVVM 框架（比如 CommunityToolkit.Mvvm、Prism 等）来说，它的入门难度极高。&lt;/p>
&lt;p>最近看了一篇列在了 ReactiveUI &lt;a class="link" href="https://www.reactiveui.net/docs/index.html" target="_blank" rel="noopener"
>官方文档&lt;/a>的“资源”中的博文：&lt;a class="link" href="https://ericsink.com/entries/dont_use_rxui.html" target="_blank" rel="noopener"
>I have become a huge fan of ReactiveUI&lt;/a>。有趣的是，当你点开这篇文章后，你会发现标题是“Don&amp;rsquo;t use ReactiveUI”。不过作者立刻在第一段就澄清了这只是一个颇具欺骗性的标题，实际上并不是想表达这个意思，作者是在说反话。&lt;/p>
&lt;p>所以我的这篇文章也仿照这一点，起了一个颇具欺骗性的标题——是的，我也在说反话。现阶段我不推荐任何人使用 RxUI，尤其是现在有 CommunityToolkit.Mvvm 这样成熟且易上手的框架的情况下（我还专门写了一整个 &lt;a class="link" href="https://mvvm.coldwind.top" target="_blank" rel="noopener"
>入门教程&lt;/a> 来和大家分享这个工具包的使用）。&lt;/p>
&lt;h2 id="为什么不推荐使用-rxui">
为什么不推荐使用 RxUI？
&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e4%b8%8d%e6%8e%a8%e8%8d%90%e4%bd%bf%e7%94%a8-rxui" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>那么为什么我不推荐使用 RxUI 呢？&lt;/p>
&lt;p>如果你或你的团队选择了 RxUI 来进行开发，那么你将获得下面几个优势：&lt;/p>
&lt;ol>
&lt;li>你的代码会看起来很酷；&lt;/li>
&lt;li>你会获得新颖而不同寻常的开发体验；&lt;/li>
&lt;li>你的代码会非常难以让新入职的同事接手，这会让你变得更加重要，也更难以被“优化”；&lt;/li>
&lt;li>你会充分培养自己的自学能力——因为你几乎无法靠别人理清楚你到底在干什么。&lt;/li>
&lt;/ol>
&lt;p>是的，如果你选择了 RxUI，那么“you are on your own.”，甚至包括目前最聪明的几个大语言模型，比如 ChatGPT o3、Claude 3.7 Sonnet、Gemini 2.0 Pro、Grok 3、DeepSeek R1 等，也帮不了你。现在不能，将来应该也不能，因为它们的训练集里面并不充分包含这么一个技术。&lt;/p>
&lt;p>可能有些开发者会说，RxUI 有你说的那么难吗？我觉得还好啊，不就是借助 &lt;code>RaiseAndSetIfChanged&lt;/code> 这个方法来实现属性的通知，用 &lt;code>ReactiveCommand&lt;/code> 来创建用于绑定 &lt;code>Command&lt;/code> 的命令，最多再用 &lt;code>ObservableAsPropertyHelper&lt;/code>（OAPH）来创建一个只读属性（或者说计算属性），就可以了啊？如果嫌麻烦，还可以借助 Fody，或者源生成器不是嘛。&lt;/p>
&lt;p>如果你这样想，那么恭喜你，你可能只是刚刚入门，并且你的项目并不复杂，仅靠入门的这些知识就可以应付。如果你深入了解 RxUI，你会发现你要考虑的因素实在是太多了，并且很多时候你可能都不知道自己在做什么，该想什么，以及该如何判断某种做法的优劣。&lt;/p>
&lt;p>不相信的话，不妨问自己这么几个问题：&lt;/p>
&lt;ul>
&lt;li>你知道视图及视图模型的生命周期，以及什么时候该使用 &lt;code>WhenActivated&lt;/code> 吗？&lt;/li>
&lt;li>你知道哪些对象应当考虑资源回收的问题吗？&lt;/li>
&lt;li>如果你不确定某个对象是否应该显式释放资源，你知道该如何去判断吗？&lt;/li>
&lt;li>你知道 &lt;code>WhenAny&lt;/code>、&lt;code>WhenAnyValue&lt;/code>、&lt;code>WhenAnyObservable&lt;/code> 这三个方法的区别以及触发时机吗？&lt;/li>
&lt;li>你知道什么时候该使用 &lt;code>Bind&lt;/code>、&lt;code>BindCommand&lt;/code> 等方法而不是直接在 XAML 中绑定吗？&lt;/li>
&lt;li>你知道 &lt;code>MessageBus&lt;/code>、&lt;code>DynamicData&lt;/code> 这些类的作用吗？&lt;/li>
&lt;li>你知道如何处理可观测对象的异常吗？&lt;/li>
&lt;li>你知道如何取消一个从可观测对象或异步任务创建的命令吗？&lt;/li>
&lt;li>你知道如何正确搭配 IoC 容器来使用 RxUI 吗？你知道 RxUI 所依赖的 Splat 吗？&lt;/li>
&lt;li>你熟悉响应式编程吗？&lt;/li>
&lt;/ul>
&lt;p>最后一个问题应该才是灵魂拷问。如果你没有拿下响应式编程的信心，那么我建议你还是先放弃 RxUI，并且也基本上不要指望可以一边学习 RxUI 一边入门响应式编程，因为响应式编程是一种思想，而这个思想在 RxUI 中只体现了一方面。但 RxUI 还需要学习的东西还有很多，比如如何遵循 MVVM 模式，如何正确使用依赖注入，如何管理视图的生命周期，如何在界面框架中去实现绑定等。如果你一边学习一边开发，那么很有可能随着你慢慢理解这一切，你会发现你之前写的代码是多么地糟糕。&lt;/p>
&lt;h2 id="当你度过重重困难最终会得到什么">
当你度过重重困难，最终会得到什么？
&lt;a href="#%e5%bd%93%e4%bd%a0%e5%ba%a6%e8%bf%87%e9%87%8d%e9%87%8d%e5%9b%b0%e9%9a%be%e6%9c%80%e7%bb%88%e4%bc%9a%e5%be%97%e5%88%b0%e4%bb%80%e4%b9%88" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>好吧。假如你历经千辛万苦，终于熟练掌握了 RxUI，那么你会得到什么呢？或者说，如果你打一开始就选择了 CommunityToolkit.Mvvm，你会损失什么？&lt;/p>
&lt;p>首先，你可以优雅地实现一个搜索框，这也是 RxUI 官方示例中经常展示的场景：用户输入文字，搜索结果实时更新，并能有效处理延迟、去重等复杂逻辑；此外，如果有一些属性及命令，它们之间的通知关系比较复杂，那么 RxUI 会让你的代码更加直观，好比于从大家的主动通知目标变成了目标去主动观察大家；更重要的是，你还入门了响应式编程这一个有趣的概念。&lt;/p>
&lt;p>然而，这种便利并非毫无代价。你可能会为了掌握 RxUI 付出大量的时间和精力。更重要的是，你的代码库可能会变得难以维护，因为其中充斥着 RxUI 特有的 API 和概念。这些 API 本身就带有一丝“黑魔法”的色彩，可能会让团队中的其他成员，甚至未来的维护者感到困惑——掌握这门框架的人才，多吗？&lt;/p>
&lt;p>遇到了困难，你去看官方文档，去问群友，去问大模型，恐怕都不会有太多的帮助。至少我并不认为，RxUI 这么复杂的框架，它的官方文档足够详尽和易懂，我也基本上找不到什么稍微深入一些的示例项目，更是几乎看不到有多少人在写关于 RxUI 的博客——那该如何指望大语言模型能够掌握这个技术呢？&lt;/p>
&lt;p>但如果你选择了别的框架，那么相关的教程实在是太多了，毕竟 MVVM 教程多得是，无非就是了解一下框架的用法，它们的视图模型的基类是如何实现 INPC 接口，并提供了一些额外的辅助方法的，就差不多了。这么直白的学习路线，是 RxUI 所不拥有的，因为后者需要更多的背景知识。&lt;/p>
&lt;p>哦对了，差点忘了一件事情：如果你选择了 RxUI，你还有可能获得一个“四不像”的项目。因为 RxUI 为了实现 MVVM，将整个响应式编程的概念引入到了项目中，但是这一概念一般情况下我们根本用不到。因此，很多人即便在自己的项目中使用了 RxUI，但是对于其他的业务逻辑，仍旧使用的是传统的思路，比如使用多线程加锁（好一点的用信号量，差一点的甚至还在用 bool 类型的标志位）、线程安全的队列、LINQ 甚至是 &lt;code>for&lt;/code> 循环，而完全不考虑 Rx 提供的数据流这一概念。&lt;/p>
&lt;p>此外，RxUI 还引入了 Splat 这个库，用于解决跨平台的问题（比如读取本地图片等），还提供了一套简单的 IoC 容器——不管你用到用不到，它都在那里。作为一个完美主义者，我非常不喜欢引入一个库，但是只用了一小部分功能这件事情。因此我更喜欢 CommunityToolkit.Mvvm，因为它只提供了 MVVM 的基础功能，我可以随意搭配其他我想用的 IoC 容器等（同理，我也并不怎么喜欢 Prism）。&lt;/p>
&lt;p>因此，权衡利弊至关重要。如果你需要处理极其复杂的响应式场景，并且愿意投入大量时间和精力去学习和维护，那么 RxUI 或许是一个不错的选择；但如果你的项目复杂度适中，或者团队成员对响应式编程不熟悉，那么选择更易上手的框架可能更为明智。毕竟，技术的选择应该服务于项目的目标，而不是反过来。&lt;/p>
&lt;h2 id="我还打不打算讲-rxui-了">
我还打不打算讲 RxUI 了？
&lt;a href="#%e6%88%91%e8%bf%98%e6%89%93%e4%b8%8d%e6%89%93%e7%ae%97%e8%ae%b2-rxui-%e4%ba%86" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>这是一个充满矛盾的问题。一方面，我写了这么多来“唱衰”RxUI，劝退新手，似乎已经给它判了“死刑”。那么，我还会继续深入讲解它吗？&lt;/p>
&lt;p>我想，答案是肯定的。尽管入门门槛高，维护成本大，但 RxUI 终究是一门有趣的技术，一个有着十几年历史却依然“新颖”的框架，一个能将不畏挑战的开发者筛选出来的概念。能够攻克它，掌握它，并将经验分享给大家，这本身就是一件充满成就感的事情。&lt;/p>
&lt;p>当然，这一天恐怕不会很快到来。首先，我对响应式编程本身的理解还需要进一步加深。其次，如果仅仅是重复介绍 &lt;code>RaiseAndSetIfChanged&lt;/code>、&lt;code>ReactiveCommand&lt;/code>、&lt;code>ObservableAsPropertyHelper&lt;/code> 这些基础用法，那毫无意义。市面上并不缺乏这种级别的教程。真正有价值的，是解决前面提到的那些深层问题，帮助大家真正理解和掌握 RxUI 的精髓。否则，互联网上只会多出一篇平庸的“教程”，而不是一篇真正具有“教育意义”的文章。&lt;/p>
&lt;p>所以，我真心期待有读者能在评论区“打脸”，分享你优秀的 RxUI 学习路线和实践经验。这不仅能让我受益，更能帮助所有对 RxUI 感兴趣的开发者。如果能看到更多人分享他们成功使用 RxUI 的案例，那将是对我观点最好的反驳，也是我最乐于见到的。毕竟，技术的世界，永远欢迎不同的声音和观点。&lt;/p></description></item><item><title>WPF 中的 ContentControl 及 ContentPresenter 有何异同？</title><link>https://blog.coldwind.top/posts/contentcontrol-vs-contentpresenter/</link><pubDate>Wed, 05 Mar 2025 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/contentcontrol-vs-contentpresenter/</guid><description>&lt;p>标题中提到的 &lt;code>ContentControl&lt;/code> 和 &lt;code>ContentPresenter&lt;/code> 都是 WPF 中较为常见的显示内容的控件，它们有各自的用途。但有的时候，开发者可能会搞不清楚它们之间的区别，导致选错了控件（却往往又因为效果实现了而忽视这一问题）。本文将简单介绍一下这两个控件的异同。&lt;/p>
&lt;h2 id="二者的共同点">
二者的共同点
&lt;a href="#%e4%ba%8c%e8%80%85%e7%9a%84%e5%85%b1%e5%90%8c%e7%82%b9" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>首先，我们来看一下 &lt;code>ContentControl&lt;/code> 和 &lt;code>ContentPresenter&lt;/code> 之间的共同点：&lt;/p>
&lt;ol>
&lt;li>都包含一个 &lt;code>Content&lt;/code> 属性；&lt;/li>
&lt;li>都可以用作一个将要展示内容的容器或占位符；&lt;/li>
&lt;li>默认情况下通常都没有什么样式，完全透明且空白，看起来十分轻量。&lt;/li>
&lt;/ol>
&lt;p>因此，当用于展示一个控件或内容时，它们看起来都可以胜任。比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ContentControl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Hello, World!&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/ContentControl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>或者我们还可以在资源词典中声明一个控件：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;MyButton&amp;#34;&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Hello, World!&amp;#34;&lt;/span> &lt;span class="na">x:Shared=&lt;/span>&lt;span class="s">&amp;#34;False&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ContentControl&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;{StaticResource MyButton}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">这里我们使用了 &lt;code>x:Shared=&amp;quot;False&amp;quot;&lt;/code> 来确保每次使用这个资源时都会创建一个新的实例。否则，如果我们在多个地方使用这个资源，那么这些地方的控件都会指向同一个实例，导致一些问题（比如只有一个控件正确显示）。&lt;/div>
&lt;/div>
&lt;p>当然了，我们还可以在代码后台去动态设置 &lt;code>Content&lt;/code> 属性，这里我们就不演示了。&lt;/p>
&lt;h2 id="二者的区别">
二者的区别
&lt;a href="#%e4%ba%8c%e8%80%85%e7%9a%84%e5%8c%ba%e5%88%ab" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>但正是因为它们有着这样的相同点，因此经常会有开发者误用了它们两个。那么，它们之间的区别又是什么呢？&lt;/p>
&lt;h3 id="它们的基类不同">
它们的基类不同
&lt;a href="#%e5%ae%83%e4%bb%ac%e7%9a%84%e5%9f%ba%e7%b1%bb%e4%b8%8d%e5%90%8c" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>&lt;code>ContentControl&lt;/code> 是 &lt;code>Control&lt;/code> 的子类，而 &lt;code>ContentPresenter&lt;/code> 是 &lt;code>FrameworkElement&lt;/code> 的子类。&lt;/p>
&lt;p>首先我们来看 &lt;code>FrameworkElement&lt;/code>。它提供了一些最基本的界面元素应该有的属性，比如宽高、样式、布局等。&lt;code>Control&lt;/code> 是它的子类，额外添加了控件需要的边框、前背景色、字体等，还有一个相当重要的属性 &lt;code>Template&lt;/code>，用于定义控件的外观。&lt;/p>
&lt;p>然后，在这些的基础上，&lt;code>ContentControl&lt;/code> 继承了 &lt;code>Control&lt;/code>，并添加了一个 &lt;code>Content&lt;/code> 属性，用于存放要展示的内容。不仅如此，因为它拥有模板（以及模板选择器等），因此它可以根据一些因素选择合适的模板从而去展示内容，并且非常适合在运行时动态改变内容。此外，它的 &lt;code>Content&lt;/code> 不仅可以是一个具体的控件，还可以是一个数据模型等（通常还可以是绑定得到的）。&lt;/p>
&lt;p>另一方面，&lt;code>ContentPresenter&lt;/code> 并不拥有 &lt;code>Control&lt;/code> 所提供的那些属性，可以说是一个相当轻量的界面元素。它的主要使用场景就是在模板中，用于展示模板的内容。比如我们可以在一个 &lt;code>Button&lt;/code> 的模板中使用 &lt;code>ContentPresenter&lt;/code> 来展示按钮的内容：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ContentTemplate&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;MyButtonTemplate&amp;#34;&lt;/span> &lt;span class="na">TargetType=&lt;/span>&lt;span class="s">&amp;#34;Button&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Border&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ContentPresenter&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Border&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/ContentTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>不仅如此，因为它太适合用在这个场景了，所以通常我们根本不需要去操作它的 &lt;code>Content&lt;/code> 属性，因为它会自动绑定到模板的 &lt;code>Content&lt;/code> 属性上。也就是说，我们不需要写 &lt;code>Content=&amp;quot;{TemplateBinding Content}&amp;quot;&lt;/code> 这样的代码。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">其实 &lt;code>ContentPresenter&lt;/code> 的作用就是在模板中用来展示 &lt;code>Content&lt;/code>，所以 &lt;code>ContentControl&lt;/code> 及其子控件自然也都用到了它。如果你在一个模板中，错误地将 &lt;code>ContentControl&lt;/code> 用在了应该用 &lt;code>ContentPresenter&lt;/code> 的地方，并且手写了 &lt;code>Content&lt;/code> 属性的绑定，那么你仍然有可能看到正确的效果。只是它底层依旧是借助了 &lt;code>ContentPresenter&lt;/code>。&lt;/div>
&lt;/div>
&lt;h3 id="它们的功能不同">
它们的功能不同
&lt;a href="#%e5%ae%83%e4%bb%ac%e7%9a%84%e5%8a%9f%e8%83%bd%e4%b8%8d%e5%90%8c" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>因为上面的原因，所以即便它们看起来（甚至简单使用起来）并没有什么区别，但是它们被设计出来的目的及作用是完全不一样的。&lt;/p>
&lt;p>&lt;code>ContentControl&lt;/code> 一般来说有这么几种用途：&lt;/p>
&lt;ol>
&lt;li>用于展示一个控件或内容（可以理解为容器或占位符）；&lt;/li>
&lt;li>用作控件的基类，可以被继承，从而实现一些自定义的控件；&lt;/li>
&lt;li>用于动态改变内容，比如在运行时改变 &lt;code>Content&lt;/code> 属性；&lt;/li>
&lt;li>搭配 &lt;code>DataTemplate&lt;/code> 使用，用于根据数据模型展示不同的内容（尤其适用于导航页面）。&lt;/li>
&lt;/ol>
&lt;p>对于第 4 点，这里有一个简单的例子：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ContentControl&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;{Binding CurrentPageViewModel}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ContentControl.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;DataTemplate&lt;/span> &lt;span class="na">DataType=&lt;/span>&lt;span class="s">&amp;#34;{x:Type local:HomePage}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;local:HomePage&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/DataTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;DataTemplate&lt;/span> &lt;span class="na">DataType=&lt;/span>&lt;span class="s">&amp;#34;{x:Type local:AboutPage}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;local:AboutPage&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/DataTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ContentControl.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/ContentControl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>此时，虽然它的 &lt;code>Content&lt;/code> 属性的值并不是一个控件，而是一个模型（Model），但是它会根据这个模型的类型自动选择合适的模板来展示内容。这样的话，只要我们给它提供视图模型（ViewModel），它就可以自动选择相应的视图（View）来展示，并且还会自动将 &lt;code>View&lt;/code> 的 &lt;code>DataContext&lt;/code> 设置为 &lt;code>Content&lt;/code> 的值。&lt;/p>
&lt;p>而 &lt;code>ContentPresenter&lt;/code> 一般来说只有一种用途，那就是用在 &lt;code>ControlTemplate&lt;/code> 中。类似的，还有一个 &lt;code>ItemsPresenter&lt;/code>，用于在 &lt;code>ItemsControl&lt;/code> （及其子控件，如 &lt;code>ListBox&lt;/code> 等）的模板中展示子项。如果你在一个需要使用 &lt;code>ContentControl&lt;/code> 的地方使用了 &lt;code>ContentPresenter&lt;/code>，那么你不仅没有办法操作 &lt;code>Template&lt;/code> 或提供 &lt;code>DataTemplate&lt;/code> 来展示数据，而且还将没有办法操作一些最基本的 &lt;code>Control&lt;/code> 的属性，比如前景色、背景色、字体等。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>深入了解 &lt;code>ContentControl&lt;/code> 和 &lt;code>ContentPresenter&lt;/code> 的区别，可能显得有些繁琐。其实大家基本上只需要记住，&lt;code>ContentPresenter&lt;/code> 几乎只用于模板中，其余情况下都可以使用 &lt;code>ContentControl&lt;/code> 即可。&lt;/p>
&lt;p>让合适的控件做合适的事情，这样才有助于我们开发出更加清晰、稳健、易维护的代码。&lt;/p></description></item><item><title>WPF 中 ObjectDataProvider 的一些有趣用法</title><link>https://blog.coldwind.top/posts/objectdataprovider-tips/</link><pubDate>Fri, 07 Feb 2025 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/objectdataprovider-tips/</guid><description>&lt;img src="https://s2.loli.net/2025/03/04/6BmTwRWGapOPoIE.webp" alt="Featured image of post WPF 中 ObjectDataProvider 的一些有趣用法" />&lt;p>WPF 中的 &lt;code>ObjectDataProvider&lt;/code> 是一个很有用的类。与常见的直接绑定到属性（包括控件的依赖属性、类的实例的属性、静态属性或字段等）不同的是，它可以通过调用构造函数的方式来创建对象，或调用对象的方法来获取数据，进而将其用作绑定的数据源。&lt;/p>
&lt;p>这篇文章我们就来探讨一下它的一些实用而有趣的用法。&lt;/p>
&lt;h2 id="基本用法">
基本用法
&lt;a href="#%e5%9f%ba%e6%9c%ac%e7%94%a8%e6%b3%95" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;h3 id="创建对象">
创建对象
&lt;a href="#%e5%88%9b%e5%bb%ba%e5%af%b9%e8%b1%a1" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>首先，我们来看一下最基本的用法：通过 &lt;code>ObjectDataProvider&lt;/code> 来创建一个对象。比如我们有一个 &lt;code>Person&lt;/code> 类：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Person&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Age&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">Person&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">age&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Age&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">age&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>如果 &lt;code>Person&lt;/code> 提供了无参构造函数，那么我们可以用传统的方式直接实例化一个对象：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;local:Person&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;PersonObject&amp;#34;&lt;/span> &lt;span class="na">Name=&lt;/span>&lt;span class="s">&amp;#34;Tom&amp;#34;&lt;/span> &lt;span class="na">Age=&lt;/span>&lt;span class="s">&amp;#34;25&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但现在我们假设 &lt;code>Person&lt;/code> 类只提供了有参构造函数，那么我们就无法直接实例化一个对象了。这时，我们可以通过 &lt;code>ObjectDataProvider&lt;/code> 来实现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="err">...&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ObjectDataProvider&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;PersonObject&amp;#34;&lt;/span> &lt;span class="na">ObjectType=&lt;/span>&lt;span class="s">&amp;#34;{x:Type local:Person}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ObjectDataProvider.ConstructorParameters&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;sys:String&amp;gt;&lt;/span>John&lt;span class="nt">&amp;lt;/sys:String&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;sys:Int32&amp;gt;&lt;/span>25&lt;span class="nt">&amp;lt;/sys:Int32&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ObjectDataProvider.ConstructorParameters&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ObjectDataProvider&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;{Binding Name, Source={StaticResource PersonObject}}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;{Binding Age, Source={StaticResource PersonObject}}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里我们通过 &lt;code>ObjectType&lt;/code> 属性指定了 &lt;code>Person&lt;/code> 类型，通过 &lt;code>ConstructorParameters&lt;/code> 属性传入了构造函数的参数。这样我们就成功地创建了一个 &lt;code>Person&lt;/code> 对象，并将其绑定到了两个 &lt;code>TextBlock&lt;/code> 控件上。&lt;/p>
&lt;h3 id="调用方法">
调用方法
&lt;a href="#%e8%b0%83%e7%94%a8%e6%96%b9%e6%b3%95" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>除了创建对象，&lt;code>ObjectDataProvider&lt;/code> 还可以调用对象的方法。比如我们有一个 &lt;code>Calculator&lt;/code> 类：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Calculator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我们可以通过如下方式调用 &lt;code>Add&lt;/code> 方法：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="err">...&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ObjectDataProvider&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;CalculatorObject&amp;#34;&lt;/span> &lt;span class="na">ObjectType=&lt;/span>&lt;span class="s">&amp;#34;{x:Type local:Calculator}&amp;#34;&lt;/span> &lt;span class="na">MethodName=&lt;/span>&lt;span class="s">&amp;#34;Add&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ObjectDataProvider.MethodParameters&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;sys:Int32&amp;gt;&lt;/span>10&lt;span class="nt">&amp;lt;/sys:Int32&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;sys:Int32&amp;gt;&lt;/span>20&lt;span class="nt">&amp;lt;/sys:Int32&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ObjectDataProvider.MethodParameters&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ObjectDataProvider&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;{Binding Source={StaticResource CalculatorObject}}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>上面的方式会自动创建一个 &lt;code>Calculator&lt;/code> 对象，并调用其 &lt;code>Add&lt;/code> 方法，传入了两个参数。我们还可以将 &lt;code>Calculator&lt;/code> 类及其成员声明为静态的，这样就可以避免创建对象了。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">如果 &lt;code>ObjectType&lt;/code> 指定的类型不是静态的，那么即便我们要访问的属性或方法是静态的，&lt;code>ObjectDataProvider&lt;/code> 也会自动创建一个对象。这一现象可以在运行时通过观察该资源的 &lt;code>ObjectInstance&lt;/code> 属性来验证。&lt;/div>
&lt;/div>
&lt;h3 id="给定实例">
给定实例
&lt;a href="#%e7%bb%99%e5%ae%9a%e5%ae%9e%e4%be%8b" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>除了指定 &lt;code>ObjectType&lt;/code> 属性外，我们还可以通过 &lt;code>ObjectInstance&lt;/code> 属性来指定一个实例。比如我们上面的 &lt;code>Calculator&lt;/code> 类提供了单例模式的实现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Calculator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="n">Calculator&lt;/span> &lt;span class="n">Instance&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>那么我们可以通过如下方式调用 &lt;code>Add&lt;/code> 方法：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="err">...&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ObjectDataProvider&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;CalculatorObject&amp;#34;&lt;/span> &lt;span class="na">ObjectInstance=&lt;/span>&lt;span class="s">&amp;#34;{x:Static local:Calculator.Instance}&amp;#34;&lt;/span> &lt;span class="na">MethodName=&lt;/span>&lt;span class="s">&amp;#34;Add&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ObjectDataProvider.MethodParameters&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;sys:Int32&amp;gt;&lt;/span>10&lt;span class="nt">&amp;lt;/sys:Int32&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;sys:Int32&amp;gt;&lt;/span>20&lt;span class="nt">&amp;lt;/sys:Int32&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ObjectDataProvider.MethodParameters&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ObjectDataProvider&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;{Binding Source={StaticResource CalculatorObject}}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>当我们指定了 &lt;code>ObjectInstance&lt;/code> 属性后，就不需要再指定 &lt;code>ObjectType&lt;/code> 属性了。&lt;/p>
&lt;h3 id="工厂模式">
工厂模式
&lt;a href="#%e5%b7%a5%e5%8e%82%e6%a8%a1%e5%bc%8f" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>还有一个经典用法是，我们可以结合工厂模式来在 XAML 中创建对象。比如我们有一个 &lt;code>PersonFactory&lt;/code> 类：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">PersonFactory&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">Person&lt;/span> &lt;span class="n">CreatePerson&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">age&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Person&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">age&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我们可以通过如下方式创建一个 &lt;code>Person&lt;/code> 对象：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="err">...&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ObjectDataProvider&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;PersonFactory&amp;#34;&lt;/span> &lt;span class="na">ObjectType=&lt;/span>&lt;span class="s">&amp;#34;{x:Type local:PersonFactory}&amp;#34;&lt;/span> &lt;span class="na">MethodName=&lt;/span>&lt;span class="s">&amp;#34;CreatePerson&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ObjectDataProvider.MethodParameters&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;sys:String&amp;gt;&lt;/span>John&lt;span class="nt">&amp;lt;/sys:String&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;sys:Int32&amp;gt;&lt;/span>25&lt;span class="nt">&amp;lt;/sys:Int32&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ObjectDataProvider.MethodParameters&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ObjectDataProvider&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样便为 XAML 注入了更多的活力与灵活性。&lt;/p>
&lt;h2 id="经典用法">
经典用法
&lt;a href="#%e7%bb%8f%e5%85%b8%e7%94%a8%e6%b3%95" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;h3 id="绑定枚举类型到下拉选单">
绑定枚举类型到下拉选单
&lt;a href="#%e7%bb%91%e5%ae%9a%e6%9e%9a%e4%b8%be%e7%b1%bb%e5%9e%8b%e5%88%b0%e4%b8%8b%e6%8b%89%e9%80%89%e5%8d%95" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>相信所有和 &lt;code>ComboBox&lt;/code> 控件打过交道的开发者都知道，如果我们希望将一个枚举类型的值绑定到 &lt;code>ComboBox&lt;/code> 控件上，我们可以通过 &lt;code>ObjectDataProvider&lt;/code> 来实现。&lt;/p>
&lt;p>比如我们有一个 &lt;code>Fruit&lt;/code> 枚举类型：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">enum&lt;/span> &lt;span class="n">Fruit&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Apple&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Banana&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Orange&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Pear&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我们可以通过如下方式将其绑定到 &lt;code>ComboBox&lt;/code> 控件上：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="err">...&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ObjectDataProvider&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;FruitEnumValues&amp;#34;&lt;/span> &lt;span class="na">MethodName=&lt;/span>&lt;span class="s">&amp;#34;GetValues&amp;#34;&lt;/span> &lt;span class="na">ObjectType=&lt;/span>&lt;span class="s">&amp;#34;{x:Type sys:Enum}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ObjectDataProvider.MethodParameters&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;x:Type&lt;/span> &lt;span class="na">TypeName=&lt;/span>&lt;span class="s">&amp;#34;local:Fruit&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ObjectDataProvider.MethodParameters&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ObjectDataProvider&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ComboBox&lt;/span> &lt;span class="na">ItemsSource=&lt;/span>&lt;span class="s">&amp;#34;{Binding Source={StaticResource FruitEnumValues}}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里其实就体现了对于类型方法的调用。首先，我们知道在 C# 中，可以通过这样的方式来获取枚举类型的所有值：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">values&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Enum&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetValues&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Fruit&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>也就是我们调用了 &lt;code>Enum&lt;/code> 类型的 &lt;code>GetValues&lt;/code> 静态方法，并传入了 &lt;code>Fruit&lt;/code> 类型（&lt;code>Type&lt;/code>），从而获取所有的枚举值。而在 XAML 中，我们就是借助 &lt;code>ObjectDataProvider&lt;/code> 来实现了相同的操作。我们通过 &lt;code>ObjectType&lt;/code> 属性指定了 &lt;code>Enum&lt;/code> 类型，通过 &lt;code>MethodName&lt;/code> 属性指定了 &lt;code>GetValues&lt;/code> 方法，再通过 &lt;code>MethodParameters&lt;/code> 属性传入了 &lt;code>Fruit&lt;/code> 类型。&lt;/p>
&lt;h3 id="为下拉选单提供整数数据源">
为下拉选单提供整数数据源
&lt;a href="#%e4%b8%ba%e4%b8%8b%e6%8b%89%e9%80%89%e5%8d%95%e6%8f%90%e4%be%9b%e6%95%b4%e6%95%b0%e6%95%b0%e6%8d%ae%e6%ba%90" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>借鉴上面的思路，我们很容易产生更多的想法：既然它可以调用任何类型的方法，那么 LINQ 的 &lt;code>Enumerable&lt;/code> 上面的一些静态方法岂不也能为我们所用了？&lt;/p>
&lt;p>那么我们就赶快来试一试吧。我们假如我们想给一个 &lt;code>ComboBox&lt;/code> 添加几个连续的数字作为它的选项（比如 1~5），在代码后台我们会这样调用：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">numbers&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Enumerable&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">5&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>那么在 XAML 中，我们可以这样实现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="na">xmlns:linq=&lt;/span>&lt;span class="s">&amp;#34;clr-namespace:System.Linq;assembly=netstandard&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ObjectDataProvider&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;Numbers&amp;#34;&lt;/span> &lt;span class="na">MethodName=&lt;/span>&lt;span class="s">&amp;#34;Range&amp;#34;&lt;/span> &lt;span class="na">ObjectType=&lt;/span>&lt;span class="s">&amp;#34;{x:Type linq:Enumerable}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ObjectDataProvider.MethodParameters&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;sys:Int32&amp;gt;&lt;/span>1&lt;span class="nt">&amp;lt;/sys:Int32&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;sys:Int32&amp;gt;&lt;/span>5&lt;span class="nt">&amp;lt;/sys:Int32&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ObjectDataProvider.MethodParameters&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ObjectDataProvider&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ComboBox&lt;/span> &lt;span class="na">ItemsSource=&lt;/span>&lt;span class="s">&amp;#34;{Binding Source={StaticResource Numbers}}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>是不是很有意思呢？&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>&lt;code>ObjectDataProvider&lt;/code> 是一个非常有用的类，它可以帮助我们在 XAML 中实现更多的功能。通过调用构造函数或对象的方法，我们可以实现更多的数据绑定操作。在实际开发中，我们可以根据具体的需求，灵活地使用 &lt;code>ObjectDataProvider&lt;/code>，从而提高开发效率。&lt;/p>
&lt;p>不过，好用归好用，它或许并不总是最优解。如果我们可以借助 &lt;code>ViewModel&lt;/code> 中的成员，或 &lt;code>MarkupExtension&lt;/code> 等方式来实现，那么我们一般就可以不考虑使用 &lt;code>ObjectDataProvider&lt;/code> 了。毕竟，&lt;code>ObjectDataProvider&lt;/code> 声明起来还是显得有些繁琐，而且因为借助反射，我们在编译时也可能无法发现一些潜在的问题。&lt;/p></description></item><item><title>为什么用户密码需要加盐哈希后再存储？</title><link>https://blog.coldwind.top/posts/why-password-hash-with-salt/</link><pubDate>Thu, 23 Jan 2025 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/why-password-hash-with-salt/</guid><description>&lt;img src="https://s2.loli.net/2025/01/23/oNjelMu2p8TmiPw.jpg" alt="Featured image of post 为什么用户密码需要加盐哈希后再存储？" />&lt;blockquote>
&lt;p>本文有对应的视频教程：&lt;a class="link" href="https://www.bilibili.com/video/BV1F4wWehErP" target="_blank" rel="noopener"
>哔哩哔哩&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>我们常说，密码不能明文存储在数据库中，而是应当哈希后存储。尤其我们还要对密码进行加盐处理。这样做的目的及必要性是什么呢？在 C# 中又该如何实现呢？这篇文章我们就来探讨一下。我们将从最不安全到最安全，逐步讲解为什么要这样做。&lt;/p>
&lt;h2 id="明文存储">
明文存储
&lt;a href="#%e6%98%8e%e6%96%87%e5%ad%98%e5%82%a8" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>首先我们就来看一看最不安全的方式吧：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">User&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Id&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="c1">// 自增主键&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Username&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Password&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="c1">// 明文密码&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后我们去创建一个用户：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">user&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">User&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Username&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;admin&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Password&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;123456&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>那么现在，数据库中存储的就是用户的明文密码了。这样其实是非常危险的。&lt;/p>
&lt;ul>
&lt;li>假如是本地的如 SQLite 数据库，那么只要有人能够访问到数据库文件，或通过反编译等方式获取到了连接字符串，那么就可以直接看到用户的密码；&lt;/li>
&lt;li>假如是远程的数据库，那么黑客依旧有多种方式可以获取到数据库的数据，比如 SQL 注入、SSH 密钥泄露、数据库备份文件泄露、其他服务器漏洞等等。&lt;/li>
&lt;/ul>
&lt;p>明文的密码可以说是相当不应该被泄露的。它不仅可能包含了用户的私密信息以及使用习惯，还可以被黑客直接用来撞库（即通过泄露的密码尝试登录其他网站）。所以我们应当在任何情况下避免明文存储密码。&lt;/p>
&lt;h2 id="哈希存储md5--sha1">
哈希存储（MD5 / SHA1）
&lt;a href="#%e5%93%88%e5%b8%8c%e5%ad%98%e5%82%a8md5--sha1" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>下面我们稍微升级一下我们的代码，改为存储使用 MD5 或 SHA1 哈希后的密码：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">User&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Id&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="c1">// 自增主键&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Username&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">PasswordHash&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="c1">// 密码哈希&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后为了方便开发，我们再写一个密码辅助类，用来进行密码哈希及验证：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">PasswordHelper&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">HashPassword&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">password&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">md5&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">MD5&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Create&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">hash&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">md5&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ComputeHash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Encoding&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">UTF8&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetBytes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">Convert&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToBase64String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hash&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">VerifyPassword&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">password&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">passwordHash&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">HashPassword&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="n">passwordHash&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在这个方法中，我们使用 MD5 算法对密码进行哈希，并最终转换为 Base64 字符串。在验证密码时，我们只需要再次哈希输入的密码，然后与数据库中的密码哈希进行比较即可。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">&lt;p>为什么我们通常会将密码转为 Base64 字符串再进行存储，而不是直接存为比如 BLOB 呢？这是因为 Base64 字符串是可读的，方便我们在数据库中查看。不仅如此，字符串的索引的效率也比 BLOB 更高。&lt;/p>
&lt;p>在后面的方法中，我们还会看到一些哈希后的密码本身就是可读字符串的方法。所以通常我们会将密码哈希转为字符串进行存储。&lt;/p>&lt;/div>
&lt;/div>
&lt;p>此时我们保存的密码可能形如：&lt;/p>
&lt;p>&lt;code>ISMvKXpXpadDiUoOSoAfww==&lt;/code>&lt;/p>
&lt;p>现在这个密码看起来显然比明文要安全多了。但很可惜，在黑客看来，这样的密码恐怕并没有安全太多，因为有一招叫做&lt;strong>彩虹表攻击&lt;/strong>。简单来说，黑客可以提前生成一张巨大的彩虹表，里面包含了常见密码的哈希值。然后黑客只需要将数据库中的哈希值与彩虹表中的哈希值进行比对，就可以很快地找到密码。&lt;/p>
&lt;p>比如上面的密码，对应的明文是 &lt;code>admin&lt;/code>。黑客只需要在彩虹表中找到对应的哈希值，就可以轻松破解密码了。可不要小看这个彩虹表，它通常包含巨量的常见密码，甚至是所有可能的密码组合。所以，除非你的密码比较复杂（比如包含大小写、数字及符号），否则可能就会被彩虹表轻易破解。&lt;/p>
&lt;p>不仅如此，MD5 和 SHA1 算法本身也是不安全的。它们已经被证明是可以被碰撞的。所谓碰撞，就是两个不同的输入可以生成相同的哈希值。这样的话，黑客就可以通过碰撞来破解密码了。以上述例子来说，虽然黑客可能无法通过彩虹表得知我们的明文是 &lt;code>admin&lt;/code>，但是他通过计算发现，&lt;code>qwerty&lt;/code> 同样可以生成相同的哈希值，那么他就可以用 &lt;code>qwerty&lt;/code> 来登录了。毕竟服务器端的校验只会比对哈希值，而不会比对明文。&lt;/p>
&lt;h2 id="使用-sha256-加盐">
使用 SHA256 加盐
&lt;a href="#%e4%bd%bf%e7%94%a8-sha256-%e5%8a%a0%e7%9b%90" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>那么，我们只好进一步升级我们的算法了。这次我们使用能够防止碰撞的 SHA256（它是 SHA-2 系列中的一种，其他常见的还有 SHA-384、SHA-512 等）算法，并且加入一个随机的盐值：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">User&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Id&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="c1">// 自增主键&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Username&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">PasswordHash&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="c1">// 密码哈希&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Salt&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="c1">// 盐值&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后，我们修改一下 &lt;code>PasswordHelper&lt;/code> 类：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">PasswordHelper&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">HashPassword&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">password&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">salt&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">passwordBytes&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Encoding&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">UTF8&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetBytes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">combinedBytes&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">passwordBytes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="n">salt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Array&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Copy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">passwordBytes&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">combinedBytes&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">passwordBytes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Array&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Copy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">salt&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">combinedBytes&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">passwordBytes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">salt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">hash&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">SHA256&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">HashData&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">combinedBytes&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">Convert&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToBase64String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hash&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">VerifyPassword&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">password&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">passwordHash&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">salt&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">HashPassword&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">salt&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="n">passwordHash&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">GenerateSalt&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">RandomNumberGenerator&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetBytes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">16&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 一般 16 字节（256 位）的盐值即可&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">&lt;p>在较新版本的 .NET 中，我们可以使用很多便利的静态方法，比如 &lt;code>SHA256.HashData&lt;/code>，&lt;code>RandomNumberGenerator.GetBytes&lt;/code> 等，而不需要我们先创建实例。&lt;/p>
&lt;p>在以前，大家可能会见过使用 &lt;code>RNGCryptoServiceProvider&lt;/code> 来生成随机数的方法。但是该方法现在已经过时。&lt;/p>&lt;/div>
&lt;/div>
&lt;p>在这个方法中，我们将密码和盐值合并后再进行哈希。这样，即使两个用户的密码相同，由于盐值不同，最终的哈希值也会不同。这样就避免了碰撞的问题。&lt;/p>
&lt;p>此外，盐值也是需要存储在数据库中的。这样，在进行密码校验时，会根据用户的 &lt;code>Id&lt;/code> 或 &lt;code>Username&lt;/code> 从数据库中取出盐值及加盐哈希后的密码，然后再将用户输入的密码使用相同的盐值进行哈希，最后与数据库中的密码进行比对，从而判断密码是否正确。&lt;/p>
&lt;p>这样的密码存储方式，即使黑客拿到了数据库，也无法直接破解密码。因为彩虹表攻击现在已经不再有效，毕竟每个用户都有不同的盐值。&lt;/p>
&lt;h2 id="使用-pbkdf2">
使用 PBKDF2
&lt;a href="#%e4%bd%bf%e7%94%a8-pbkdf2" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>SHA256 加盐的方式已经相当安全了，但是我们还可以进一步提升安全性。因为黑客虽然无法使用彩虹表，但仍然可以尝试暴力破解密码。简单来说，黑客可以尝试使用各种密码组合，然后通过哈希后的密码与数据库中的密码进行比对，从而破解密码。&lt;/p>
&lt;p>所以，为了提高密码被暴力破解的难度，之后我们要考虑的方案基本上就是围绕着提高计算的速度来展开。首先，我们可以考虑使用 PBKDF2 算法。这个算法在很多编程语言的标准库中均有提供。在 C# 中，我们可以使用 &lt;code>Rfc2898DeriveBytes&lt;/code> 类来实现。我们只需要稍加修改我们的 &lt;code>PasswordHelper&lt;/code> 类即可：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">PasswordHelper&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">HashPassword&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">password&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">salt&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">pbkdf2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Rfc2898DeriveBytes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">salt&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">10000&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">HashAlgorithmName&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SHA256&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 迭代 10000 次&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">Convert&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToBase64String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pbkdf2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetBytes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">32&lt;/span>&lt;span class="p">));&lt;/span> &lt;span class="c1">// 32 字节（256 位）的哈希值&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 其他方法不变&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">&lt;code>Rfc2898&lt;/code> 是 &lt;code>PBKDF2&lt;/code> 的一个实现，所以这里可以说是一回事，只是名字不同。另外，&lt;code>Rfc2898DeriveBytes&lt;/code> 的构造函数中，我们需要给定使用的哈希算法，否则不包含这一传参的构造函数会提示已过时。&lt;/div>
&lt;/div>
&lt;p>在这个方法中，我们使用 &lt;code>Rfc2898DeriveBytes&lt;/code> 类来进行密码哈希。我们可以指定迭代次数，这样就可以提高计算的速度。一般来说，迭代次数越多，计算的速度就越慢，黑客破解密码的难度就越大。但是，迭代次数也不能太多，否则会影响用户登录的速度。一般来说，&lt;code>10000&lt;/code> 次迭代是一个比较合适的值。&lt;/p>
&lt;p>有了这一算法的加持，现在黑客想要暴力破解，需要付出的代价就会大大增加。&lt;/p>
&lt;h2 id="使用-bcrypt-和-argon2">
使用 BCrypt 和 Argon2
&lt;a href="#%e4%bd%bf%e7%94%a8-bcrypt-%e5%92%8c-argon2" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>但可惜的是，道高一尺，魔高一丈。PBKDF2 算法虽然提高了黑客暴力破解密码的难度，但是仍然有一些问题。比如，黑客可以使用 GPU 或 FPGA 来加速计算，从而提高暴力破解的速度。所以，我们还有更加重量级的选手：BCrypt 及 Argon2。&lt;/p>
&lt;p>我们先来看 BCrypt。在 C# 中，我们可以使用 &lt;a class="link" href="https://github.com/BcryptNet/bcrypt.net" target="_blank" rel="noopener"
>&lt;code>BCrypt.Net-Next&lt;/code>&lt;/a> 库来实现 BCrypt 算法。我们只需要稍加修改我们的 &lt;code>PasswordHelper&lt;/code> 类即可：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">PasswordHelper&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">HashPassword&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">password&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">BCrypt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Net&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">BCrypt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">HashPassword&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">12&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 12 为工作因子&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">VerifyPassword&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">password&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">passwordHash&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">BCrypt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Net&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">BCrypt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Verify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">password&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">passwordHash&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>并且我们的 &lt;code>User&lt;/code> 类也可以去掉 &lt;code>Salt&lt;/code> 属性了。这是为什么呢？因为 BCrypt 算法本身就包含了盐值，相当于替我们代劳了。这样，我们就不需要再自己生成盐值，也不需要专门去存储盐值了。&lt;/p>
&lt;p>我们看一个 BCrypt 哈希后的密码：&lt;/p>
&lt;p>&lt;code>$2a$11$lraBT1/lH3RiFXjQbywREutDElnBFaolPOEsDAvo1sjK2iRjwCAUi&lt;/code>&lt;/p>
&lt;p>这段文本中，&lt;code>$2a$&lt;/code> 表示使用的是 BCrypt 算法，&lt;code>11&lt;/code> 表示工作因子，而后面的内容则是由盐值和哈希后的密码组成。也就是说，这段文本中包含了全部用来验证密码的信息，我们只需要将其存储在数据库中即可。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">工作因子是用来控制计算的速度的，它是 $2$ 的幂运算。比如，上面的密码就对应了 $2^{11}=2048$ 次计算。工作因子越大，计算的速度就越慢，黑客破解密码的难度就越大。通常 $10$ 到 $12$ 是一个比较合适的范围。&lt;/div>
&lt;/div>
&lt;p>但黑客依旧不甘心，还是打算借助其强大的硬件来尝试破解。这样，我们就要请出我们的杀手锏：Argon2 算法了。&lt;/p>
&lt;p>与 BCrypt 一样，Argon2 同样没有 .NET 标准库的实现。我们可以选择一些第三方的库，比如 &lt;a class="link" href="https://github.com/kmaragon/Konscious.Security.Cryptography" target="_blank" rel="noopener"
>&lt;code>Konscious.Security.Cryptography&lt;/code>&lt;/a>。&lt;/p>
&lt;p>这里，我们不演示实际在 C# 中该如何使用 Argon2 算法，因为它与 BCrypt 在开发体验及数据模型和表的设计上是类似的。但是，Argon2 算法在安全性上要比 BCrypt 更胜一筹。它引入了更多防止黑客暴力破解的机制，比如内存硬化、并行计算等。它可以轻易调整破解的时间、内存成本以及并行度。&lt;/p>
&lt;p>另外，Argon2 还提供了三种变体：Argon2d、Argon2i 和 Argon2id。其中，Argon2d 适用于对抗时间攻击，Argon2i 适用于对抗侧信道攻击，而 Argon2id 则是两者的结合。具体来说：&lt;/p>
&lt;ul>
&lt;li>Argon2d 更注重防止 GPU 并行计算的攻击。&lt;/li>
&lt;li>Argon2i 更注重抗侧信道攻击。&lt;/li>
&lt;li>Argon2id 是综合了这两种特性，适合一般用途。&lt;/li>
&lt;/ul>
&lt;p>相信有了这么“变态”的密码哈希算法，至少现阶段的黑客是彻底束手无策了。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在这篇文章中，我们从最不安全的明文存储密码开始，逐步讲解了为什么我们需要对密码进行加盐哈希。我们看到了明文存储密码的危险性，哈希后的密码可能被彩虹表攻击的问题。以及老旧的哈希算法可能存在的被碰撞的问题。然后，我们介绍了 SHA256 加盐、PBKDF2、BCrypt 和 Argon2 等算法，以及它们的优缺点。&lt;/p>
&lt;p>在实际开发中，我们应当根据自己的需求和安全性要求来选择适合的密码哈希算法。对于一般的小项目来说，SHA256 加盐已经足够安全了，而且它对于客户端及服务端开销的要求也很低。但是，如果我们对安全性要求很高，那么 BCrypt 或 Argon2 就是不二之选了。&lt;/p></description></item><item><title>如何在 WPF 中高效布局多行多列的控件</title><link>https://blog.coldwind.top/posts/grid-of-controls/</link><pubDate>Thu, 26 Dec 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/grid-of-controls/</guid><description>&lt;img src="https://s2.loli.net/2024/12/26/kgGyXcYIlnMNpSr.jpg" alt="Featured image of post 如何在 WPF 中高效布局多行多列的控件" />&lt;p>这次我们来探讨一个 WPF 中的简单而又不简单的布局问题：如何实现下图中的效果？&lt;/p>
&lt;figure>&lt;img src="https://s2.loli.net/2024/12/25/qlpQr1DJ4FBskKe.png" width="400px">
&lt;/figure>
&lt;p>为什么说这个问题简单而又不简单呢？是因为单纯只是实现这个效果的话，我们完全可以使用 &lt;code>Grid&lt;/code> 控件来实现。&lt;del>甚至如果完全不在乎优雅的话，我们还可以用 &lt;code>Canvas&lt;/code> 来实现&lt;/del>（当然这几乎在任何情况下都是不推荐的）。但是，实际这样干过的开发者相信都很清楚，使用 &lt;code>Grid&lt;/code> 的实现方式几乎可以说是毫无灵活性的。很快就会被后续的界面微调的需求整得焦头烂额。&lt;/p>
&lt;p>那么我们今天就来看一看，这样的需求通常有哪些做法吧。希望这篇文章中提到的某些方式能够帮助到你。&lt;/p>
&lt;h2 id="最传统的方式grid">
最传统的方式：Grid
&lt;a href="#%e6%9c%80%e4%bc%a0%e7%bb%9f%e7%9a%84%e6%96%b9%e5%bc%8fgrid" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在介绍后面的更好的方式之前，我们有必要先来看一看最传统的方式：使用 &lt;code>Grid&lt;/code> 控件。这样才能更好地分析这种方式的局限性，以及思考改进的方向。&lt;/p>
&lt;p>这样的方式相信所有具备基本 WPF 基础的开发者都相当熟悉：&lt;/p>
&lt;ol>
&lt;li>在 XAML 中定义一个 &lt;code>Grid&lt;/code> 控件&lt;/li>
&lt;li>在 &lt;code>Grid&lt;/code> 中定义多个 &lt;code>RowDefinition&lt;/code> 和 &lt;code>ColumnDefinition&lt;/code>&lt;/li>
&lt;li>在 &lt;code>Grid&lt;/code> 中定义多个控件，并通过 &lt;code>Grid.Row&lt;/code> 和 &lt;code>Grid.Column&lt;/code> 来指定控件的位置&lt;/li>
&lt;li>（可选）通过 &lt;code>Grid.RowSpan&lt;/code> 和 &lt;code>Grid.ColumnSpan&lt;/code> 来指定控件的跨行和跨列&lt;/li>
&lt;/ol>
&lt;p>好，然后我们就开始写 XAML 吧：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Grid.RowDefinitions&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;RowDefinition&lt;/span> &lt;span class="na">Height=&lt;/span>&lt;span class="s">&amp;#34;Auto&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;RowDefinition&lt;/span> &lt;span class="na">Height=&lt;/span>&lt;span class="s">&amp;#34;Auto&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;RowDefinition&lt;/span> &lt;span class="na">Height=&lt;/span>&lt;span class="s">&amp;#34;Auto&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;RowDefinition&lt;/span> &lt;span class="na">Height=&lt;/span>&lt;span class="s">&amp;#34;Auto&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Grid.RowDefinitions&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Grid.ColumnDefinitions&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ColumnDefinition&lt;/span> &lt;span class="na">Width=&lt;/span>&lt;span class="s">&amp;#34;Auto&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ColumnDefinition&lt;/span> &lt;span class="na">Width=&lt;/span>&lt;span class="s">&amp;#34;Auto&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Grid.ColumnDefinitions&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Label&lt;/span> &lt;span class="na">Grid.Row=&lt;/span>&lt;span class="s">&amp;#34;0&amp;#34;&lt;/span> &lt;span class="na">Grid.Column=&lt;/span>&lt;span class="s">&amp;#34;0&amp;#34;&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;1&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="na">Grid.Row=&lt;/span>&lt;span class="s">&amp;#34;0&amp;#34;&lt;/span> &lt;span class="na">Grid.Column=&lt;/span>&lt;span class="s">&amp;#34;1&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Label&lt;/span> &lt;span class="na">Grid.Row=&lt;/span>&lt;span class="s">&amp;#34;1&amp;#34;&lt;/span> &lt;span class="na">Grid.Column=&lt;/span>&lt;span class="s">&amp;#34;0&amp;#34;&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;2&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="na">Grid.Row=&lt;/span>&lt;span class="s">&amp;#34;1&amp;#34;&lt;/span> &lt;span class="na">Grid.Column=&lt;/span>&lt;span class="s">&amp;#34;1&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- ... --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>好的，马上痛苦面具就戴上了：为每个控件写 &lt;code>Grid.Row&lt;/code> 和 &lt;code>Grid.Column&lt;/code>，简直就是顶级折磨。如果你搞了十几行，结果被告知需要删掉前面某一行，或者在前面添加一行，那就更是欲哭无泪了。&lt;/p>
&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">可能会有聪明的同学想到一些简化这一步骤的方式，比如使用 &lt;code>Style&lt;/code> 来统一指定上面的 &lt;code>TextBox&lt;/code> 控件的 &lt;code>Grid.Column&lt;/code>，并省略 &lt;code>Label&lt;/code> 的 &lt;code>Grid.Column=&amp;quot;0&amp;quot;&lt;/code>。但这样依旧存在相当多的局限性，毕竟它最怕的就是例外情况了。&lt;/div>
&lt;/div>
&lt;p>所以，我们显然是不能满足于使用这样的方式的。那究竟有什么更好的方式呢？&lt;/p>
&lt;h2 id="外层使用-uniformgrid">
外层使用 UniformGrid
&lt;a href="#%e5%a4%96%e5%b1%82%e4%bd%bf%e7%94%a8-uniformgrid" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>说起“自动化布局”，相信很多人在使用 &lt;code>Grid&lt;/code> 时都垂涎过 &lt;code>UniformGrid&lt;/code> 的功能，因为它可以让所有子控件依次填充到每个单元格中。所以，我们或许可以借助它的这一效果。比如我们想要一个两列的网格，可以：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;UniformGrid&lt;/span> &lt;span class="na">Columns=&lt;/span>&lt;span class="s">&amp;#34;2&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Label&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;1&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Label&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;2&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- ... --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但这样的局限性在于，所有子控件的大小（或者它们所处的单元格）都是完全等大的。这可能会失去灵活性，并且看起来有些呆板。那么我们还可以部分借助 &lt;code>UniformGrid&lt;/code> 的功能，比如下面这样：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;UniformGrid&lt;/span> &lt;span class="na">Columns=&lt;/span>&lt;span class="s">&amp;#34;1&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;UniformGrid.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Style&lt;/span> &lt;span class="na">TargetType=&lt;/span>&lt;span class="s">&amp;#34;StackPanel&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Setter&lt;/span> &lt;span class="na">Property=&lt;/span>&lt;span class="s">&amp;#34;Orientation&amp;#34;&lt;/span> &lt;span class="na">Value=&lt;/span>&lt;span class="s">&amp;#34;Horizontal&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Style&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Style&lt;/span> &lt;span class="na">TargetType=&lt;/span>&lt;span class="s">&amp;#34;Label&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Setter&lt;/span> &lt;span class="na">Property=&lt;/span>&lt;span class="s">&amp;#34;Width&amp;#34;&lt;/span> &lt;span class="na">Value=&lt;/span>&lt;span class="s">&amp;#34;50&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Style&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/UniformGrid.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Label&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;1&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Label&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;2&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- ... --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/UniformGrid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里我们将每一行的内容放在了一个 &lt;code>StackPanel&lt;/code> 中，从而让 &lt;code>Label&lt;/code> 和 &lt;code>TextBox&lt;/code> 出现在同一行（或者说 &lt;code>UniformGrid&lt;/code> 的同一个单元格中）。这样我们就不需要再为每个控件指定 &lt;code>Grid.Row&lt;/code> 和 &lt;code>Grid.Column&lt;/code> 了。&lt;/p>
&lt;p>为了更加简化代码，我们还声明了两个 &lt;code>Style&lt;/code>，分别用于设置 &lt;code>StackPanel&lt;/code> 的 &lt;code>Orientation&lt;/code> 和 &lt;code>Label&lt;/code> 的 &lt;code>Width&lt;/code>。这样我们就可以在 &lt;code>StackPanel&lt;/code> 中直接放置 &lt;code>Label&lt;/code> 和 &lt;code>TextBox&lt;/code>，而不需要再为 &lt;code>Label&lt;/code> 设置宽度了。&lt;/p>
&lt;p>但是这样做有一个明显的局限性：行高是一致的。如果我们需要不同行的高度不一致，那么这种方式就无法满足需求了。&lt;/p>
&lt;h2 id="外层使用-stackpanel">
外层使用 StackPanel
&lt;a href="#%e5%a4%96%e5%b1%82%e4%bd%bf%e7%94%a8-stackpanel" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>为了突破上一个方式的局限性，我们可以考虑使用 &lt;code>StackPanel&lt;/code> 来替代 &lt;code>UniformGrid&lt;/code>。这样我们就可以为每一行的 &lt;code>StackPanel&lt;/code> 设置不同的高度了。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;StackPanel&lt;/span> &lt;span class="na">Orientation=&lt;/span>&lt;span class="s">&amp;#34;Vertical&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;StackPanel.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- Styles --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/StackPanel.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;StackPanel&lt;/span> &lt;span class="na">Height=&lt;/span>&lt;span class="s">&amp;#34;40&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Label&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;1&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;StackPanel&lt;/span> &lt;span class="na">Height=&lt;/span>&lt;span class="s">&amp;#34;50&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Label&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;2&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- ... --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但很快，我们又会被别的问题所折磨：如何高效地设置行间距？&lt;/p>
&lt;p>而且可能还有另外一个小折磨：因为内外都使用了 &lt;code>StackPanel&lt;/code>，所以写的 &lt;code>Style&lt;/code> 可能会被它们共同使用。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">针对这一问题，可能有同学会将内部的 &lt;code>StackPanel&lt;/code> 替换为 &lt;code>WrapPanel&lt;/code>，但这样并不是一个好主意，因为 &lt;code>WrapPanel&lt;/code> 会让每一行的控件都尽可能地靠左对齐，并且在宽度不够时会自动换行！或许现在看着还好，但调整了窗口或父控件的尺寸，甚至修改了分辨率及缩放后，都有可能让你的界面变得一团糟。&lt;/div>
&lt;/div>
&lt;p>此时，我们可以请个“外援”。其实在 Win UI 3、Avalonia UI 等框架中，&lt;code>StackPanel&lt;/code> 天生就比 WPF 多了一个属性：&lt;code>Spacing&lt;/code>。这个属性可以让我们很方便地设置行间距。但是在 WPF 中，我们就没有这么幸运了。不过，我们可以借助自定义控件来实现这一功能。&lt;/p>
&lt;p>代码也不用我们自己写，网上可以找到一些开源实现。比如这里我贴两个供大家参考：&lt;/p>
&lt;ul>
&lt;li>&lt;a class="link" href="https://github.com/Kinnara/ModernWpf/blob/master/ModernWpf/Controls/SimpleStackPanel.cs" target="_blank" rel="noopener"
>Kinnara/ModernWpf - SimpleStackPanel&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://github.com/OrgEleCho/EleCho.WpfSuite/blob/master/EleCho.WpfSuite.Layouts/Layouts/StackPanel.cs" target="_blank" rel="noopener"
>OrgEleCho/WpfSuite - StackPanel&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>假如我们搬运到了 &lt;code>local&lt;/code> 命名空间下，那么我们就可以这样使用：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;local:StackPanel&lt;/span> &lt;span class="na">Spacing=&lt;/span>&lt;span class="s">&amp;#34;10&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;local:StackPanel.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Style&lt;/span> &lt;span class="na">TargetType=&lt;/span>&lt;span class="s">&amp;#34;StackPanel&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Setter&lt;/span> &lt;span class="na">Property=&lt;/span>&lt;span class="s">&amp;#34;Orientation&amp;#34;&lt;/span> &lt;span class="na">Value=&lt;/span>&lt;span class="s">&amp;#34;Horizontal&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Style&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/local:StackPanel.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Label&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;1&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Label&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;2&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- ... --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/local:StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样看起来就方便多了。此外，我们还有一些小技巧，来进一步提高布局的效率：&lt;/p>
&lt;ol>
&lt;li>为 &lt;code>Label&lt;/code> 设置一个固定的宽度，这样可以让所有的 &lt;code>Label&lt;/code> 对齐&lt;/li>
&lt;li>为 &lt;code>Label&lt;/code> 设置 &lt;code>VerticalAlignment&lt;/code> 或 &lt;code>VerticalContentAlignment&lt;/code> 为 &lt;code>Center&lt;/code>，这样可以让 &lt;code>Label&lt;/code> 和 &lt;code>TextBox&lt;/code> 垂直居中对齐&lt;/li>
&lt;li>为 &lt;code>TextBox&lt;/code> 等控件（还比如 &lt;code>CheckBox&lt;/code>、&lt;code>ComboBox&lt;/code> 等）设置固定的宽度，这样可以让所有的 &lt;code>TextBox&lt;/code> 对齐&lt;/li>
&lt;/ol>
&lt;h2 id="使用更高级的-grid">
使用更高级的 Grid
&lt;a href="#%e4%bd%bf%e7%94%a8%e6%9b%b4%e9%ab%98%e7%ba%a7%e7%9a%84-grid" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>上面的方式其实已经相当灵活了，尤其是对于可能需要增删、调换顺序之类的情形。但是可能仍然会觉得不够爽，因为里面多出了一层 &lt;code>StackPanel&lt;/code>，这写起来就还是会觉得不太爽。&lt;/p>
&lt;p>所以，有没有办法让 &lt;code>Grid&lt;/code> 更加智能一点，比如可以像是 &lt;code>WrapPanel&lt;/code> 或者 &lt;code>UniformGrid&lt;/code> 那样，自动填充控件呢？&lt;/p>
&lt;p>答案当然是有的。这里给大家推荐一个 NuGet 包：&lt;code>WpfAutoGrid&lt;/code>。这个 &lt;code>AutoGrid&lt;/code> 就正是我们梦寐以求的控件。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">NuGet 上有好多个叫这个名字的包。这里我贴一个自己试过的：&lt;a class="link" href="https://github.com/budul100/WpfAutoGrid.Core" target="_blank" rel="noopener"
>&lt;code>WpfAutoGrid.Core&lt;/code>&lt;/a>&lt;/div>
&lt;/div>
&lt;p>它的使用也非常简单：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;local:AutoGrid&lt;/span> &lt;span class="na">Columns=&lt;/span>&lt;span class="s">&amp;#34;100,150&amp;#34;&lt;/span> &lt;span class="na">RowCount=&lt;/span>&lt;span class="s">&amp;#34;8&amp;#34;&lt;/span> &lt;span class="na">RowHeight=&lt;/span>&lt;span class="s">&amp;#34;30&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Label&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;1&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Label&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;2&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- ... --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/local:AutoGrid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样我们连内部的 &lt;code>StackPanel&lt;/code> 都不需要了，直接将 &lt;code>Label&lt;/code> 和 &lt;code>TextBox&lt;/code> 放在 &lt;code>AutoGrid&lt;/code> 中即可。此外，它提供了很多实用的属性，比如上面实用的 &lt;code>Columns&lt;/code>、&lt;code>RowCount&lt;/code>、&lt;code>RowHeight&lt;/code>，还有 &lt;code>ColumnSpacing&lt;/code>、&lt;code>RowSpacing&lt;/code> 等。不仅如此，&lt;code>AutoGrid&lt;/code> 还能正确响应子控件 &lt;code>ColumnSpan&lt;/code> 等属性，可以让我们更加灵活地布局控件。&lt;/p>
&lt;p>除此之外，这里我再贴一个我自己写的 &lt;code>GridAssist&lt;/code>。它是一个附加属性，可以直接用于原生的 &lt;code>Grid&lt;/code> 控件。使用这个附加属性，就能够自动为子控件添加 &lt;code>Grid.Row&lt;/code> 和 &lt;code>Grid.Column&lt;/code> 属性。&lt;/p>
&lt;p>&lt;a class="link" href="https://gist.github.com/BYJRK/66aefb80c838634e0642ffed4f58e076" target="_blank" rel="noopener"
>BYJRK/GridAssist&lt;/a>&lt;/p>
&lt;p>使用方式也非常简单：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Grid&lt;/span> &lt;span class="na">local:GridAssist.AutoRowColumn=&lt;/span>&lt;span class="s">&amp;#34;_,2&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Label&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;1&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Label&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;2&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- ... --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这个附加属性的 &lt;code>AutoRowColumn&lt;/code> 填写的 &lt;code>_,2&lt;/code> 意思是，行数自动增加，列数固定为 2（还可以写 &lt;code>_,2,Auto&lt;/code>，从而将列宽设置为 &lt;code>Auto&lt;/code>）。这样我们就不需要为每个控件指定 &lt;code>Grid.Row&lt;/code> 和 &lt;code>Grid.Column&lt;/code> 了。然后，我们只需要将子控件按照从上到下，从左到右的顺序放置即可，不需要再引入一层 &lt;code>StackPanel&lt;/code> 了。此外，它也是可以正确响应 &lt;code>Grid.ColumnSpan&lt;/code> 的。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在 WPF 中，布局是一个非常重要的问题。而对于多行多列的控件布局，我们可以使用 &lt;code>Grid&lt;/code>、&lt;code>UniformGrid&lt;/code>、&lt;code>StackPanel&lt;/code>、&lt;code>AutoGrid&lt;/code> 等控件或者附加属性来实现。每一种方式都有它的优势和局限性，我们可以根据实际情况来选择最适合的方式。&lt;/p>
&lt;p>另外，对于上面提到的几种自定义控件或附加属性，我们完全可以直接将代码复制到自己的项目中，然后根据实际需求进行修改。这样可以更好地适应自己的项目，也可以更好地理解这些控件或属性的实现原理，还可以省去引入第三方库的麻烦。&lt;/p></description></item><item><title>WPF 值转换器（ValueConverter）的一些实用技巧</title><link>https://blog.coldwind.top/posts/valueconverter-tips-and-tricks/</link><pubDate>Wed, 18 Dec 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/valueconverter-tips-and-tricks/</guid><description>&lt;img src="https://s2.loli.net/2024/12/18/dRbx2KJsHOmaPG7.webp" alt="Featured image of post WPF 值转换器（ValueConverter）的一些实用技巧" />&lt;p>本篇文章对应的教学视频链接：&lt;a class="link" href="https://www.bilibili.com/video/BV1ThkHYnEgi" target="_blank" rel="noopener"
>WPF中值转换器（ValueConverter）的一些实用技巧&lt;/a>&lt;/p>
&lt;p>在 WPF 中，值转换器（&lt;code>ValueConverter&lt;/code>）是一个非常重要的概念。它可以帮助我们在绑定数据时，将数据转换成我们需要的格式。在这篇文章中，我们将介绍一些值转换器的实用技巧。&lt;/p>
&lt;h2 id="使用-wpf-内置的值转换器">
使用 WPF 内置的值转换器
&lt;a href="#%e4%bd%bf%e7%94%a8-wpf-%e5%86%85%e7%bd%ae%e7%9a%84%e5%80%bc%e8%bd%ac%e6%8d%a2%e5%99%a8" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>WPF 内置了几个常用的值转换器，我们可以直接使用。例如，我们可以使用 &lt;code>BooleanToVisibilityConverter&lt;/code> 将布尔值转换成 &lt;code>Visibility&lt;/code> 枚举值。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;BooleanToVisibilityConverter&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;BooleanToVisibilityConverter&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;CheckBox&lt;/span> &lt;span class="na">x:Name=&lt;/span>&lt;span class="s">&amp;#34;checkBox&amp;#34;&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Show Text&amp;#34;&lt;/span> &lt;span class="na">IsChecked=&lt;/span>&lt;span class="s">&amp;#34;True&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;Hello, World!&amp;#34;&lt;/span> &lt;span class="na">Visibility=&lt;/span>&lt;span class="s">&amp;#34;{Binding ElementName=checkBox, Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}}&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>遗憾的是，WPF 内置的值转换器并不是很多，基本上我们能直接用上的就是上面提到的这个 &lt;code>BooleanToVisibilityConverter&lt;/code>。其他虽然有一些照理说用得上的值转换器，但它们很多都是 &lt;code>internal&lt;/code> 的，我们无法直接使用。即便如此，通过阅读它们的源代码，我们仍然可以学习一下它们的实现方式。比如：&lt;/p>
&lt;ul>
&lt;li>&lt;a class="link" href="https://source.dot.net/#Microsoft.VisualStudio.LanguageServices/Utilities/EnumBoolConverter.cs" target="_blank" rel="noopener"
>&lt;code>EnumBoolConverter&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://source.dot.net/#Microsoft.VisualStudio.LanguageServices/Utilities/BooleanReverseConverter.cs" target="_blank" rel="noopener"
>&lt;code>BooleanReverseConverter&lt;/code>&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="将值转换器声明为单例">
将值转换器声明为单例
&lt;a href="#%e5%b0%86%e5%80%bc%e8%bd%ac%e6%8d%a2%e5%99%a8%e5%a3%b0%e6%98%8e%e4%b8%ba%e5%8d%95%e4%be%8b" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>使用值转换器有些是否会让人觉得繁琐，因为通常这意味着我们需要在某个控件的 &lt;code>Resources&lt;/code> 中声明一个值转换器，并在需要的地方通过 &lt;code>StaticResource&lt;/code> 来引用它。但实际上，我们可以将值转换器声明为单例，这样就可以在任何地方直接使用它。比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">BooleanToVisibilityConverter&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">IValueConverter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 单例模式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="n">BooleanToVisibilityConverter&lt;/span> &lt;span class="n">Instance&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">Convert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span> &lt;span class="n">targetType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">ConvertBack&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span> &lt;span class="n">targetType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后我们就可以在 XAML 中借助 &lt;code>x:Static&lt;/code> 来直接使用这个单例：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;CheckBox&lt;/span> &lt;span class="na">x:Name=&lt;/span>&lt;span class="s">&amp;#34;checkBox&amp;#34;&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Show Text&amp;#34;&lt;/span> &lt;span class="na">IsChecked=&lt;/span>&lt;span class="s">&amp;#34;True&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;Hello, World!&amp;#34;&lt;/span> &lt;span class="na">Visibility=&lt;/span>&lt;span class="s">&amp;#34;{Binding ElementName=checkBox, Path=IsChecked, Converter={x:Static local:BooleanToVisibilityConverter.Instance}}&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice warning">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-triangle" aria-hidden="true">&lt;/i>Warning
&lt;/div>
&lt;div class="notice-content">这样确实可以一定程度上简化我们的代码，但也要注意，这样做可能会导致值转换器的状态被共享，从而引发一些问题。所以在使用这种方式时，一定要确保值转换器是无状态的。&lt;/div>
&lt;/div>
&lt;h2 id="将值转换器声明在-appxaml-中">
将值转换器声明在 App.xaml 中
&lt;a href="#%e5%b0%86%e5%80%bc%e8%bd%ac%e6%8d%a2%e5%99%a8%e5%a3%b0%e6%98%8e%e5%9c%a8-appxaml-%e4%b8%ad" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>如果我们有一些相当&lt;strong>通用且无状态&lt;/strong>的值转换器，就比如 &lt;code>BooleanToVisibilityConverter&lt;/code>、&lt;code>BoolReverseConverter&lt;/code>、&lt;code>NotNullConverter&lt;/code> 等，我们可以将它们声明在 &lt;code>App.xaml&lt;/code> 中，这样就可以在整个应用程序中直接使用这些值转换器，而不需要在每个用到它们的 &lt;code>Window&lt;/code>、&lt;code>UserControl&lt;/code> 等地方都进行声明。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Application&lt;/span> &lt;span class="err">...&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Application.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;BooleanToVisibilityConverter&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;BooleanToVisibilityConverter&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;BoolReverseConverter&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;BoolReverseConverter&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;NotNullConverter&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;NotNullConverter&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Application.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Application&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>如果觉得这样的方式会“污染”&lt;code>App.xaml&lt;/code>，我们也可以新建一个 &lt;code>ResourceDictionary&lt;/code>，并将这些值转换器声明在这个 &lt;code>ResourceDictionary&lt;/code> 中，然后在 &lt;code>App.xaml&lt;/code> 中引用这个 &lt;code>ResourceDictionary&lt;/code>。例如，我们可以新建一个 &lt;code>CommonConverters.xaml&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ResourceDictionary&lt;/span> &lt;span class="err">...&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;BooleanToVisibilityConverter&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;BooleanToVisibilityConverter&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;BoolReverseConverter&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;BoolReverseConverter&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;NotNullConverter&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;NotNullConverter&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/ResourceDictionary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后在 &lt;code>App.xaml&lt;/code> 中引用这个 &lt;code>ResourceDictionary&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Application&lt;/span> &lt;span class="err">...&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Application.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ResourceDictionary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ResourceDictionary.MergedDictionaries&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ResourceDictionary&lt;/span> &lt;span class="na">Source=&lt;/span>&lt;span class="s">&amp;#34;CommonConverters.xaml&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ResourceDictionary.MergedDictionaries&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ResourceDictionary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Application.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Application&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="使用-markupextension-简化值转换器的使用">
使用 &lt;code>MarkupExtension&lt;/code> 简化值转换器的使用
&lt;a href="#%e4%bd%bf%e7%94%a8-markupextension-%e7%ae%80%e5%8c%96%e5%80%bc%e8%bd%ac%e6%8d%a2%e5%99%a8%e7%9a%84%e4%bd%bf%e7%94%a8" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>&lt;code>Markup&lt;/code> 语法也就是我们经常在 XAML 中看到的“花括号”语法，例如：&lt;/p>
&lt;ul>
&lt;li>&lt;code>{Binding ...}&lt;/code>&lt;/li>
&lt;li>&lt;code>{StaticResource ...}&lt;/code>&lt;/li>
&lt;li>&lt;code>{x:Static ...}&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>只要我们让值转换器继承 &lt;code>MarkupExtension&lt;/code>，我们就可以在 XAML 中直接使用 &lt;code>Markup&lt;/code> 语法来引用这个值转换器。比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">BooleanToVisibilityConverter&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">MarkupExtension&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">IValueConverter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">IsReversed&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">UseHidden&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">Convert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span> &lt;span class="n">targetType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">b&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">IsReversed&lt;/span> &lt;span class="p">?&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="n">b&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="p">?&lt;/span> &lt;span class="n">Visibility&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Visible&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">UseHidden&lt;/span> &lt;span class="p">?&lt;/span> &lt;span class="n">Visibility&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Hidden&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">Visibility&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Collapsed&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">ConvertBack&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span> &lt;span class="n">targetType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="n">Visibility&lt;/span> &lt;span class="n">visibility&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">visibility&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="n">Visibility&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Visible&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">override&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">ProvideValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">IServiceProvider&lt;/span> &lt;span class="n">serviceProvider&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>上面是一个“高级版”的 &lt;code>BooleanToVisibilityConverter&lt;/code>，它支持 &lt;code>IsReversed&lt;/code> 和 &lt;code>UseHidden&lt;/code> 两个属性，也就为这一值转换器提供了定制性。我们可以在 XAML 中这样使用它：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;CheckBox&lt;/span> &lt;span class="na">x:Name=&lt;/span>&lt;span class="s">&amp;#34;checkBox&amp;#34;&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Show Text&amp;#34;&lt;/span> &lt;span class="na">IsChecked=&lt;/span>&lt;span class="s">&amp;#34;True&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;Hello, World!&amp;#34;&lt;/span> &lt;span class="na">Visibility=&lt;/span>&lt;span class="s">&amp;#34;{Binding ElementName=checkBox, Path=IsChecked, Converter={local:BooleanToVisibilityConverter IsReversed=True, UseHidden=True}}&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这一技巧尤其适用于某个定制功能强大，且使用频率较高的值转换器。但也要注意，这样声明就会导致值转换器每次都会实例化一个新的出来。如果一个值转换器是无状态的，那么我们最好将其声明为单例，或者将其声明在 &lt;code>App.xaml&lt;/code> 中，从而避免重复实例化。&lt;/p>
&lt;p>如果还觉得不过瘾，我们可以为值转换器写一个抽象基类，从而进一步简化值转换器的实现。比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">abstract&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">BaseValueConverter&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">MarkupExtension&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">IValueConverter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">override&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">ProvideValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">IServiceProvider&lt;/span> &lt;span class="n">serviceProvider&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">abstract&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">Convert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span> &lt;span class="n">targetType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">virtual&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">ConvertBack&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span> &lt;span class="n">targetType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">Binding&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">DoNothing&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>另外还有一种版本，就是希望基类还顺便提供单例模式，那么我们可以这样：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">abstract&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">BaseValueConverter&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">MarkupExtension&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">IValueConverter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">where&lt;/span> &lt;span class="n">T&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">class&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="n">T&lt;/span> &lt;span class="n">Instance&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后我们就可以使用它了。比如说我们希望实现一个单向的值转换器，将字符串转换成大写，可以这样：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">StringToUpperConverter&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">BaseValueConverter&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">StringToUpperConverter&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">override&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">Convert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span> &lt;span class="n">targetType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToUpper&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">Binding&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">DoNothing&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>是不是瞬间变得简单了很多呢？&lt;/p>
&lt;h2 id="返回-donothing-与-unsetvalue">
返回 DoNothing 与 UnsetValue
&lt;a href="#%e8%bf%94%e5%9b%9e-donothing-%e4%b8%8e-unsetvalue" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在 WPF 中，有两个特殊的返回类型，分别是 &lt;code>Binding.DoNothing&lt;/code> 和 &lt;code>DependencyProperty.UnsetValue&lt;/code>。在某些情况下，让值转换器的方法返回这两个值是非常有用的。&lt;/p>
&lt;p>这两个都表示“不做任何事情”，但它们的使用场景是不同的。具体来说，&lt;code>Binding.DoNothing&lt;/code> 单纯意味着“不做任何事情”，不去通知任何绑定源或目标，也不会更新界面；而 &lt;code>DependencyProperty.UnsetValue&lt;/code> 则暗示绑定是失败的，或者值是无效的。此时，它会触发 &lt;code>Binding&lt;/code> 的 &lt;code>FallbackValue&lt;/code>，也就是俗称的“缺省值”。&lt;/p>
&lt;p>比如我们有一个可以让用户输入文件路径的文本框，并且我们会让另一个 &lt;code>TextBlock&lt;/code> 展示这个文件的名称。但是如果用户输入的路径是无效的，我们就不希望展示这个文件的名称，而是展示一个缺省值，这时我们就可以使用 &lt;code>DependencyProperty.UnsetValue&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">FilePathToFileNameConverter&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">IValueConverter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">Convert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span> &lt;span class="n">targetType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">path&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="n">File&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetFileName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">DependencyProperty&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">UnsetValue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后我们可以在 XAML 中这样使用：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="na">x:Name=&lt;/span>&lt;span class="s">&amp;#34;textBox&amp;#34;&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;C:\Users\Public\Documents\file.txt&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;{Binding ElementName=textBox, Path=Text, Converter={local:FilePathToFileNameConverter}, FallbackValue=&amp;#39;Invalid File Path&amp;#39;}&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样，当用户输入的文件路径无效时，&lt;code>TextBlock&lt;/code> 就会展示“Invalid File Path”。&lt;/p>
&lt;p>类似地，如果我们希望用户在输入无效的文件路径时，不做任何事情（比如保留上次有效的文件名城），我们就可以返回 &lt;code>Binding.DoNothing&lt;/code>。然后就可以实现相应的效果了。&lt;/p>
&lt;p>这两个特殊的返回值看似不起眼，但是如果上述功能让我们在 ViewModel 中去实现，就会变得非常繁琐。所以在这种情况下，值转换器就显得非常有用了。&lt;/p>
&lt;h2 id="借助-cultureinfo-实现多语言支持">
借助 CultureInfo 实现多语言支持
&lt;a href="#%e5%80%9f%e5%8a%a9-cultureinfo-%e5%ae%9e%e7%8e%b0%e5%a4%9a%e8%af%ad%e8%a8%80%e6%94%af%e6%8c%81" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>值转换器中的两个方法都有一个 &lt;code>CultureInfo&lt;/code> 类型的参数，我们可以利用这个参数来实现多语言支持。比如我们有一个值转换器，将数字转换成各国语言的数字。此时我们就可以在值转换器中根据 &lt;code>CultureInfo&lt;/code> 所包含的地区码来选择合适的语言。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">NumberToLocalizedNumberConverter&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">IValueConverter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">Convert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span> &lt;span class="n">targetType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">number&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">switch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">culture&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">TwoLetterISOLanguageName&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="s">&amp;#34;zh&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">number&lt;/span> &lt;span class="k">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">0&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="s">&amp;#34;零&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">1&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="s">&amp;#34;一&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">2&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="s">&amp;#34;二&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">3&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="s">&amp;#34;三&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">4&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="s">&amp;#34;四&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">5&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="s">&amp;#34;五&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="s">&amp;#34;en&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">number&lt;/span> &lt;span class="k">switch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">0&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="s">&amp;#34;Zero&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">1&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="s">&amp;#34;One&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">2&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="s">&amp;#34;Two&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">3&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="s">&amp;#34;Three&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">4&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="s">&amp;#34;Four&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">5&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="s">&amp;#34;Five&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">default&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">number&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToString&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">Binding&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">DoNothing&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后我们可以在 XAML 中这样使用：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;{Binding Number, Converter={local:NumberToLocalizedNumberConverter}, ConverterCulture=zh-CN}&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>或者我们也可以在程序中动态地设置 &lt;code>CultureInfo&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">culture&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">CultureInfo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;zh-CN&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Thread&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CurrentThread&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CurrentCulture&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Thread&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CurrentThread&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CurrentUICulture&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但是这样还不够，因为值转换器的这一入参是从控件的 &lt;code>Language&lt;/code> 属性中继承而来的。所以我们还需要修改全局的 &lt;code>Language&lt;/code> 属性。例如，我们想在一个 &lt;code>UserControl&lt;/code> 中使用中文，可以这样：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;UserControl&lt;/span> &lt;span class="na">Language=&lt;/span>&lt;span class="s">&amp;#34;zh-CN&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- 也可以这样写 --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;UserControl.Language&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;XmlLanguage&amp;gt;&lt;/span>zh-CN&lt;span class="nt">&amp;lt;/XmlLanguage&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/UserControl.Language&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/UserControl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>或者，我们还可以用 &lt;code>OverrideMetadata&lt;/code> 的方式来修改全局的 &lt;code>Language&lt;/code> 属性：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="n">FrameworkElement&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">LanguageProperty&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">OverrideMetadata&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">typeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">FrameworkElement&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="n">FrameworkPropertyMetadata&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">XmlLanguage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetLanguage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CultureInfo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CurrentCulture&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IetfLanguageTag&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样就可以让值转换器获取到当前的 &lt;code>CultureInfo&lt;/code>，从而实现多语言支持。&lt;/p>
&lt;h2 id="仿照-avalonia-ui-实现一个-funcvalueconverter">
仿照 Avalonia UI 实现一个 FuncValueConverter
&lt;a href="#%e4%bb%bf%e7%85%a7-avalonia-ui-%e5%ae%9e%e7%8e%b0%e4%b8%80%e4%b8%aa-funcvalueconverter" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>Avalonia UI 中有一个有趣的 &lt;code>FuncValueConverter&lt;/code>，它允许我们直接在代码后台简单地声明一个值转换器，而不需要额外写一个类。它地源代码可以在 &lt;a class="link" href="https://github.com/AvaloniaUI/Avalonia/blob/38e839997d2c204548e6fad396c178780a010cb1/src/Avalonia.Base/Data/Converters/FuncValueConverter.cs" target="_blank" rel="noopener"
>GitHub&lt;/a> 上看到。我们可以仿照这个实现一个类似的值转换器。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">sealed&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">FuncValueConverter&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">TIn&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">TOut&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">IValueConverter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">Func&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">TIn&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">TOut&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_convert&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">FuncValueConverter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Func&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">TIn&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">TOut&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">convert&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_convert&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">convert&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">Convert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span> &lt;span class="n">targetType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="n">TIn&lt;/span> &lt;span class="n">t&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_convert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">Binding&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">DoNothing&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">ConvertBack&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span> &lt;span class="n">targetType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">Binding&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">DoNothing&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>对于 &lt;code>Convert&lt;/code> 方法的实现，这里还有一种更好的方式。我们都知道，在 XAML 书写的很多资源，WPF 都会在底层帮我们进行合适的类型转换。比如我们将 &lt;code>&amp;quot;1&amp;quot;&lt;/code> 字符串赋值给一个 &lt;code>int&lt;/code> 类型的属性，WPF 会自动将其转换成 &lt;code>1&lt;/code>；我们将 &lt;code>&amp;quot;Visible&amp;quot;&lt;/code> 字符串赋值给一个 &lt;code>Visibility&lt;/code> 枚举类型的属性，WPF 也会进行相应的转换。如果我们不提供这个功能，那么我们写的这个 &lt;code>FuncValueConverter&lt;/code> 就会变得不够灵活。因此，我们可以借助 .NET 原生的 &lt;code>TypeDescriptor&lt;/code> 类来实现这个功能。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">Convert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span> &lt;span class="n">targetType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="n">not&lt;/span> &lt;span class="n">TIn&lt;/span> &lt;span class="n">t&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">default&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">TOut&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">TypeDescriptor&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetConverter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">TIn&lt;/span>&lt;span class="p">)).&lt;/span>&lt;span class="n">CanConvertFrom&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetType&lt;/span>&lt;span class="p">()))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">TIn&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">TypeDescriptor&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetConverter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">typeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">TIn&lt;/span>&lt;span class="p">)).&lt;/span>&lt;span class="n">ConvertFrom&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">Binding&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">DoNothing&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_convert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样我们就可以声明并使用了。我们需要将它声明为静态属性：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MainViewModel&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">ViewModelBase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="n">FuncValueConverter&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">StringToIntConverter&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后我们就可以在 XAML 中这样使用：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="na">x:Name=&lt;/span>&lt;span class="s">&amp;#34;textBox&amp;#34;&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;123&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;{Binding ElementName=textBox, Path=Text, Converter={x:Static local:MainViewModel.StringToIntConverter}}&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样，我们就能够轻易地在代码后台声明一个值转换器了。&lt;/p>
&lt;h2 id="其他第三方库">
其他第三方库
&lt;a href="#%e5%85%b6%e4%bb%96%e7%ac%ac%e4%b8%89%e6%96%b9%e5%ba%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>除了上面提到的这些方法，我们还可以使用一些第三方库来简化值转换器及绑定的使用。比如：&lt;/p>
&lt;ul>
&lt;li>&lt;a class="link" href="https://github.com/thomasgalliker/ValueConverters.NET" target="_blank" rel="noopener"
>ValueConverters.NET&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://github.com/Alex141/CalcBinding" target="_blank" rel="noopener"
>CalcBinding&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://github.com/levitali/CompiledBindings" target="_blank" rel="noopener"
>CompiledBindings&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>这些库有的提供了丰富的内置值转换器，包括组合多种值转换器的功能（例如先将字符串根据 &lt;code>IsNullOrEmpty&lt;/code> 转为 &lt;code>bool&lt;/code> 类型，再转为 &lt;code>Visibility&lt;/code> 类型），有的提供了更加强大的绑定功能，例如可以调用函数，进行数学运算等等。大家有兴趣的话可以去了解一下。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>值转换器是 WPF 中非常重要的一个概念，它可以帮助我们将数据转换成我们需要的格式。在这篇文章中，我们介绍了一些值转换器的实用技巧。希望这些技巧能够帮助大家更好地使用值转换器。&lt;/p>
&lt;p>只有充分发挥 WPF 中各个功能的优势，我们才能更好地提高我们的开发效率，实现更加复杂的功能。希望大家能够在实际的开发中多多尝试，多多实践。&lt;/p></description></item><item><title>如何在 C# 中拷贝一个文件夹</title><link>https://blog.coldwind.top/posts/how-to-copy-folder/</link><pubDate>Wed, 11 Dec 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/how-to-copy-folder/</guid><description>&lt;img src="https://s2.loli.net/2024/12/11/9swekVbJFzX3DfH.jpg" alt="Featured image of post 如何在 C# 中拷贝一个文件夹" />&lt;p>拷贝文件夹听起来是一个非常简单的任务，但是在 C# 中实现起来却并不是那么容易，因为 .NET 并没有提供内置的方法，所以通常我们只能自己来实现。&lt;/p>
&lt;p>本文提供了三种拷贝文件夹的方式供大家参考。&lt;/p>
&lt;h2 id="方法一使用递归">
方法一：使用递归
&lt;a href="#%e6%96%b9%e6%b3%95%e4%b8%80%e4%bd%bf%e7%94%a8%e9%80%92%e5%bd%92" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>使用递归是一个非常直观的方法，同时也是 &lt;a class="link" href="https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-copy-directories" target="_blank" rel="noopener"
>Microsoft Learn&lt;/a> 给出的示例。其原版的代码有些冗余和不必要的内存开销，所以这里贴一个相对简练且高效的版本：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">CopyDirectory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">sourceFolderPath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">targetFolderPath&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Directory&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CreateDirectory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">targetFolderPath&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">filePath&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">Directory&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetFiles&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sourceFolderPath&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">string&lt;/span> &lt;span class="n">fileName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetFileName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filePath&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">string&lt;/span> &lt;span class="n">destinationPath&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Combine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">targetFolderPath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">fileName&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">File&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Copy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filePath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">destinationPath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">directoryPath&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">Directory&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetDirectories&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sourceFolderPath&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">string&lt;/span> &lt;span class="n">directoryName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetFileName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">directoryPath&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">string&lt;/span> &lt;span class="n">destinationPath&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Combine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">targetFolderPath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">directoryName&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">CopyDirectory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">directoryPath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">destinationPath&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>简单来说，这个方法会递归地拷贝源文件夹下的所有文件和子文件夹到目标文件夹中。对于子文件夹，会递归调用该方法进行拷贝。&lt;/p>
&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">&lt;code>Directory.CreateDirectory&lt;/code> 是一个相当灵活的方法。如果目标文件夹不存在，它会自动创建；如果目标文件夹已经存在，它会忽略这个操作。同时，它还会沿途创建所有不存在的文件夹（类似 &lt;code>mkdir&lt;/code> 的 &lt;code>-p&lt;/code> 参数）。&lt;/div>
&lt;/div>
&lt;h2 id="方法二不使用递归">
方法二：不使用递归
&lt;a href="#%e6%96%b9%e6%b3%95%e4%ba%8c%e4%b8%8d%e4%bd%bf%e7%94%a8%e9%80%92%e5%bd%92" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>如果不希望使用递归，那么也可以通过相对路径的方式来实现。这个方法会递归搜索源文件夹下的所有文件，通过计算它与源文件夹的相对路径来得到它的目标路径，进而生成目标路径所在的文件夹。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">CopyDirectory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">sourceFolderPath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">targetFolderPath&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Directory&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CreateDirectory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">targetFolderPath&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">filePath&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">Directory&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetFiles&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sourceFolderPath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;*.*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">SearchOption&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AllDirectories&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">relativePath&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetRelativePath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sourceFolderPath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">filePath&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">targetFilePath&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Combine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">targetFolderPath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">relativePath&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">subTargetFolderPath&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetDirectoryName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">targetFilePath&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">subTargetFolderPath&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Directory&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CreateDirectory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">subTargetFolderPath&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">File&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Copy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filePath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">targetFilePath&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">&lt;code>Path.GetDirectoryName&lt;/code> 方法有可能返回空。这一情况通常发生在文件位于根目录的情况（例如 Windows 的 &lt;code>C:\&lt;/code>，或 Unix 的 &lt;code>/&lt;/code>）。&lt;/div>
&lt;/div>
&lt;h2 id="使用-visualbasic-的内置方法">
使用 VisualBasic 的内置方法
&lt;a href="#%e4%bd%bf%e7%94%a8-visualbasic-%e7%9a%84%e5%86%85%e7%bd%ae%e6%96%b9%e6%b3%95" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>其实 .NET 也不是完全没有提供内置的方法。比如我们可以使用 VisualBasic 的 &lt;code>Microsoft.VisualBasic.Devices&lt;/code> 命名空间下的 &lt;code>Computer&lt;/code> 类上的 &lt;code>FileSystem&lt;/code> 成员的方法来实现拷贝文件夹的功能：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">Microsoft.VisualBasic.Devices&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">Microsoft.VisualBasic.FileIO&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">CopyDirectory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">sourceFolderPath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">targetFolderPath&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">fs&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Computer&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">FileSystem&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">fs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CopyDirectory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sourceFolderPath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">targetFolderPath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">UIOption&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnlyErrorDialogs&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>可能有读者想说，作者你怎么不早点拿出这个方法呢？这方法多么地简单易用啊！&lt;/p>
&lt;p>实际上，这个方法也是有显著缺点的：&lt;strong>需要使用 WinForms 相关的库&lt;/strong>。也就是说，你的项目需要 &lt;code>TargetFramework&lt;/code> 包含 &lt;code>-windows&lt;/code>，并且还要 &lt;code>UseWindowsForms&lt;/code>。&lt;/p>
&lt;p>如果你在开发 WPF 或 WinForms 程序，那么这通常是可以接受的。但如果你是在开发控制台程序、ASP.NET 程序，又或者 Avalonia UI 等跨平台框架，那么这个方法显然就有些 unacceptable 了。&lt;/p>
&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">其实 &lt;code>VisualBasic&lt;/code> 还提供了一些别的实用功能，比如将文件移至回收站，就可以用 &lt;code>FileSystem.DeleteFile&lt;/code> 方法，并添加 &lt;code>RecycleOption.SendToRecycleBin&lt;/code> 参数来实现。这个方法会将文件移至回收站，而不是直接删除。&lt;/div>
&lt;/div>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>本文介绍了三种拷贝文件夹的方法，分别是使用递归、不使用递归、以及使用 VisualBasic 的内置方法。这三种方法各有优劣，读者可以根据自己的需求来选择适合的方法。&lt;/p>
&lt;div class="notice warning">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-triangle" aria-hidden="true">&lt;/i>Warning
&lt;/div>
&lt;div class="notice-content">在拷贝文件夹时，一定要注意文件夹的权限问题。如果源文件夹或目标文件夹的权限不足，那么拷贝操作可能会失败。&lt;/div>
&lt;/div></description></item><item><title>常见图片相关的数据类型之间的转换</title><link>https://blog.coldwind.top/posts/image-datatypes-conversion/</link><pubDate>Tue, 19 Nov 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/image-datatypes-conversion/</guid><description>&lt;img src="https://s2.loli.net/2024/11/19/bqvEn9ipfu7DPem.jpg" alt="Featured image of post 常见图片相关的数据类型之间的转换" />&lt;p>我们在做 .NET 开发时，经常要和各种图片的数据类型打交道。&lt;strong>这里指的“类型”并不是图片的文件类型，比如 jpg、png、bmp 等，而是图片数据在内存中的表示方式&lt;/strong>。这些类型之间的转换，有时候会让人感到困惑。本文总结了常见的图片数据类型之间的转换方法，希望能帮助大家理清思路。&lt;/p>
&lt;p>常见的图片数据类型有：&lt;/p>
&lt;ul>
&lt;li>&lt;code>byte[]&lt;/code> 字节数组：可能有两种情况：
&lt;ul>
&lt;li>将图片文件读取到内存后得到的字节数组，包括图片文件的文件头等&lt;/li>
&lt;li>图片的像素数据，比如 RGB 数据&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;code>Stream&lt;/code>：数据流，比如 &lt;code>MemoryStream&lt;/code>、&lt;code>FileStream&lt;/code> 等，一般和字节数组可以轻易地相互转换&lt;/li>
&lt;li>&lt;code>Bitmap&lt;/code>：WinForms 中的图片数据类型（基于 GDI+），命名空间是 &lt;code>System.Drawing&lt;/code>&lt;/li>
&lt;li>&lt;code>BitmapImage&lt;/code>：WPF 中的图片数据类型，命名空间是 &lt;code>System.Windows.Media.Imaging&lt;/code>，常用于 &lt;code>Image&lt;/code> 控件的 &lt;code>Source&lt;/code> 属性（是 &lt;code>ImageSource&lt;/code> 类型）&lt;/li>
&lt;li>&lt;code>BitmapSource&lt;/code>：WPF 中的图片数据类型，命名空间是 &lt;code>System.Windows.Media&lt;/code>，是 &lt;code>BitmapImage&lt;/code> 的基类&lt;/li>
&lt;li>其他一些来自第三方库的图片类型&lt;/li>
&lt;/ul>
&lt;h2 id="将图片文件路径转为-bitmapimage">
将图片文件路径转为 BitmapImage
&lt;a href="#%e5%b0%86%e5%9b%be%e7%89%87%e6%96%87%e4%bb%b6%e8%b7%af%e5%be%84%e8%bd%ac%e4%b8%ba-bitmapimage" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>如果我们知道图片的链接（可以是本地链接或网址），并且想让 &lt;code>Image&lt;/code> 控件显示这个图片，最简单的方式如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">image&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Image&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">image&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Source&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">BitmapImage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="n">Uri&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">@&amp;#34;path\to\image.jpg&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">UriKind&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">RelativeOrAbsolute&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>上述方式甚至都不需要指定图片的格式，因为 &lt;code>BitmapImage&lt;/code> 和 &lt;code>BitmapDecoder&lt;/code> 都会自动进行处理。&lt;strong>对于大多数常见的图片格式（如 JPG、PNG、BMP、GIF、TIFF、WebP、HEIC、AVIF 等），这几种方式都能正常工作&lt;/strong>。但如果是一些不太常见的图片格式，则可能需要借助一些第三方库才行了。&lt;/p>
&lt;p>另外，如果我们并没有图片的路径，只有它被读进内存后的数据类型，那么就需要下面的几种方式了。&lt;/p>
&lt;h2 id="bitmap-转为-bitmapimage">
Bitmap 转为 BitmapImage
&lt;a href="#bitmap-%e8%bd%ac%e4%b8%ba-bitmapimage" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>&lt;code>System.Drawing.Bitmap&lt;/code> 和 &lt;code>System.Windows.Media.Imaging.BitmapImage&lt;/code> 是两个常见的图片数据类型。前者是 WinForms 中的类型（GDI+），后者是 WPF 的类型。它们之间的转换方法如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Drawing&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Windows.Media.Imaging&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="n">BitmapImage&lt;/span> &lt;span class="n">ConvertBitmapToBitmapImage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Bitmap&lt;/span> &lt;span class="n">bitmap&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">stream&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MemoryStream&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bitmap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Save&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ImageFormat&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Png&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">stream&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Position&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">bitmapImage&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">BitmapImage&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bitmapImage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">BeginInit&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bitmapImage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CacheOption&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">BitmapCacheOption&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnLoad&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bitmapImage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">StreamSource&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">stream&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bitmapImage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">EndInit&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bitmapImage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Freeze&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// （可选）冻结图片，提高性能和线程安全性&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">bitmapImage&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="字节数组转为-imagesource">
字节数组转为 ImageSource
&lt;a href="#%e5%ad%97%e8%8a%82%e6%95%b0%e7%bb%84%e8%bd%ac%e4%b8%ba-imagesource" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>这里有两种情况。如果字节数组只是读进内存的图片文件数据，比如一个本地的 JPG、PNG、BMP 等格式的文件，那么非常简单：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.IO&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Windows.Media.Imaging&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="n">ImageSource&lt;/span> &lt;span class="n">ConvertByteArrayToImageSource&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">bytes&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">stream&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MemoryStream&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bytes&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">bitmapImage&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">BitmapImage&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bitmapImage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">BeginInit&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bitmapImage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CacheOption&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">BitmapCacheOption&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnLoad&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bitmapImage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">StreamSource&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">stream&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bitmapImage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">EndInit&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bitmapImage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Freeze&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">bitmapImage&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>还有一种更简单的方式，直接使用 &lt;code>BitmapDecoder&lt;/code> 类：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Windows.Media.Imaging&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="n">ImageSource&lt;/span> &lt;span class="n">ConvertByteArrayToImageSource&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">bytes&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">stream&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MemoryStream&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bytes&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">BitmapDecoder&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">BitmapCreateOptions&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">PreservePixelFormat&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">BitmapCacheOption&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnLoad&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Frames&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>如果字节数组是图片的像素数据，比如从左上到右下的逐行 RGB 数据，那么会麻烦一些，而且我们需要有办法知道图片的宽高等信息：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Windows.Media&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Windows.Media.Imaging&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="n">ImageSource&lt;/span> &lt;span class="n">BgrByteArrayToImageSource&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">array&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">width&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">height&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">channel&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int?&lt;/span> &lt;span class="n">stride&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">bmp&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">WriteableBitmap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">width&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">height&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">96&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">96&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">PixelFormats&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Bgr24&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">stride&lt;/span> &lt;span class="p">??=&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="n">width&lt;/span> &lt;span class="p">*&lt;/span> &lt;span class="n">channel&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="m">3&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">/&lt;/span> &lt;span class="m">4&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">*&lt;/span> &lt;span class="m">4&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bmp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WritePixels&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="n">Int32Rect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">width&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">height&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">array&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">stride&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bmp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Freeze&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">bmp&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="bitmapsource-转为-bitmapimage">
BitmapSource 转为 BitmapImage
&lt;a href="#bitmapsource-%e8%bd%ac%e4%b8%ba-bitmapimage" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>这两个类其实是有继承关系的，&lt;code>BitmapImage&lt;/code> 继承自 &lt;code>BitmapSource&lt;/code>。但一般我们仍然需要进行一个“转换”，因为通常的使用场景是，我们从 WPF 提供的剪贴板 API 中获取到一个 &lt;code>BitmapSource&lt;/code>，但我们经过简单的处理，将它转为 &lt;code>BitmapImage&lt;/code> 从而添加给 &lt;code>Image&lt;/code> 控件。这时候可以这样转换：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Windows.Media.Imaging&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="n">BitmapImage&lt;/span> &lt;span class="n">ConvertBitmapSourceToBitmapImage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BitmapSource&lt;/span> &lt;span class="n">bitmapSource&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">bitmapImage&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">BitmapImage&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">stream&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MemoryStream&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">BitmapEncoder&lt;/span> &lt;span class="n">encoder&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">BmpBitmapEncoder&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 一般情况下，剪贴板中的图片数据是 BMP 格式的，而非 PNG 格式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">encoder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Frames&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BitmapFrame&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bitmapSource&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">encoder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Save&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">stream&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Position&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bitmapImage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">BeginInit&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bitmapImage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CacheOption&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">BitmapCacheOption&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnLoad&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bitmapImage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">StreamSource&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">stream&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bitmapImage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">EndInit&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bitmapImage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Freeze&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">bitmapImage&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="emgucvimage-转为-bitmapimage">
Emgu.CV.Image 转为 BitmapImage
&lt;a href="#emgucvimage-%e8%bd%ac%e4%b8%ba-bitmapimage" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>前面我们提到，&lt;code>BitmapImage&lt;/code> 支持绝大多数常见的图片格式。但如果现在我们有一个不常见的格式，比如 JP2（JPEG 2000）格式，那么 &lt;code>BitmapImage&lt;/code> 就无法直接处理了。这时候我们可以使用 Emgu.CV 库，它是 OpenCV 的 .NET 封装，支持更多的图片格式。下面给出一种方式：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">filename&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">@&amp;#34;path\to\image.jp2&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">mat&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Image&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Bgr&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Byte&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">bytes&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">mat&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToJpegData&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">stream&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MemoryStream&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bytes&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">bitmap&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">BitmapImage&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">bitmap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">BeginInit&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">bitmap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CacheOption&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">BitmapCacheOption&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnLoad&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">bitmap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">StreamSource&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">stream&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">bitmap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">EndInit&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">bitmap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Freeze&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">control&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Image&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">control&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Source&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">bitmap&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">control&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Dump&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>看了这么多，大家相信已经看出规律了吧？是的，对于大多数情况，我们都要先将数据转为持有常见图像类型的 &lt;code>Stream&lt;/code>，然后再创建 &lt;code>BitmapImage&lt;/code>，最后将其赋给 &lt;code>Image&lt;/code> 控件。这样的方式，可以保证我们的代码在大多数情况下都能正常工作。&lt;/p></description></item><item><title>WPF 中的 Name 与 x:Name 究竟是什么区别？</title><link>https://blog.coldwind.top/posts/wpf-name-vs-xname/</link><pubDate>Thu, 10 Oct 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/wpf-name-vs-xname/</guid><description>&lt;img src="https://s2.loli.net/2024/10/11/jurJoLAN3aWBgZ6.png" alt="Featured image of post WPF 中的 Name 与 x:Name 究竟是什么区别？" />&lt;p>在 WPF 开发中，我们可以给控件添加 &lt;code>Name&lt;/code> 或 &lt;code>x:Name&lt;/code> 属性。这样做的目的通常是希望在代码后台能够访问这个控件，或者我们在写 &lt;code>Binding&lt;/code> 表达式时，希望使用 &lt;code>ElementName&lt;/code> 的方式绑定某个控件。那么这二者究竟是什么区别呢？本文就来简单探讨一下。&lt;/p>
&lt;h2 id="本质不同但却又几乎相同">
本质不同，但却又几乎相同
&lt;a href="#%e6%9c%ac%e8%b4%a8%e4%b8%8d%e5%90%8c%e4%bd%86%e5%8d%b4%e5%8f%88%e5%87%a0%e4%b9%8e%e7%9b%b8%e5%90%8c" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>别的暂且不谈，我们只关注 XML 文档的命名空间，不难发现 &lt;code>Name&lt;/code> 和 &lt;code>x:Name&lt;/code> 的区别在于前者没有命名空间，而后者有一个 &lt;code>x&lt;/code> 命名空间。具体来说，通常我们的一个 XAML 文件的根元素是这样的：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="na">x:Class=&lt;/span>&lt;span class="s">&amp;#34;WpfApp1.MainWindow&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns=&lt;/span>&lt;span class="s">&amp;#34;http://schemas.microsoft.com/winfx/2006/xaml/presentation&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:x=&lt;/span>&lt;span class="s">&amp;#34;http://schemas.microsoft.com/winfx/2006/xaml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">Title=&lt;/span>&lt;span class="s">&amp;#34;MainWindow&amp;#34;&lt;/span> &lt;span class="na">Height=&lt;/span>&lt;span class="s">&amp;#34;450&amp;#34;&lt;/span> &lt;span class="na">Width=&lt;/span>&lt;span class="s">&amp;#34;800&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- 省略其他内容 --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>其中，&lt;code>xmlns&lt;/code> 是默认的命名空间，而 &lt;code>xmlns:x&lt;/code> 是 &lt;code>x&lt;/code> 命名空间。所以，&lt;code>x:Name&lt;/code> 和 &lt;code>Name&lt;/code> 分别出自哪个命名空间，就不言而喻了。&lt;/p>
&lt;p>但是，虽然它们两个出身不同，但在 WPF 中，它们的作用几乎是一样的。具体来说，&lt;code>Name&lt;/code> 是 &lt;a class="link" href="https://source.dot.net/#PresentationFramework/System/Windows/FrameworkElement.cs,3213" target="_blank" rel="noopener"
>&lt;code>FrameworkElement&lt;/code> 类&lt;/a>（以及 &lt;a class="link" href="https://source.dot.net/#PresentationFramework/System/Windows/FrameworkContentElement.cs,834" target="_blank" rel="noopener"
>&lt;code>FrameworkContentElement&lt;/code> 类&lt;/a>，下略）的一个依赖属性，形如（为便于阅读，代码略有删改）：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="na">[CommonDependencyProperty]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">DependencyProperty&lt;/span> &lt;span class="n">NameProperty&lt;/span> &lt;span class="p">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">DependencyProperty&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Register&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">typeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">typeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">FrameworkElement&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="n">FrameworkPropertyMetadata&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="cm">/* ... */&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[Localizability(LocalizationCategory.NeverLocalize)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[MergableProperty(false)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[DesignerSerializationOptions(DesignerSerializationOptions.SerializeAsAttribute)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">get&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">GetValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">NameProperty&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">set&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">SetValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">NameProperty&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>而进一步观察 &lt;a class="link" href="https://source.dot.net/#PresentationFramework/System/Windows/Generated/FrameworkElement.cs,30" target="_blank" rel="noopener"
>&lt;code>FrameworkElement&lt;/code> 类的声明&lt;/a>，我们可以发现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">namespace&lt;/span> &lt;span class="nn">System.Windows&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [RuntimeNamePropertyAttribute(&amp;#34;Name&amp;#34;)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">partial&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">FrameworkElement&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里的 &lt;code>RuntimeNamePropertyAttribute&lt;/code> 是一个特性，它告诉 WPF 运行时，&lt;code>FrameworkElement&lt;/code> 类的 &lt;code>Name&lt;/code> 属性将会被转为 &lt;code>x:Name&lt;/code> 属性。所以，&lt;code>Name&lt;/code> 和 &lt;code>x:Name&lt;/code> 在 WPF 中几乎是一样的。&lt;/p>
&lt;p>至于为什么要这样设计，我并没有找到官方的答案。唯一合理的猜测，就是想给开发者一个较为方便的方式去给控件命名。毕竟，&lt;code>Name&lt;/code> 比 &lt;code>x:Name&lt;/code> 看起来更简洁，更加直观（毕竟这看起来就是属于控件自己的名字一样），而且还不需要使用命名空间。&lt;/p>
&lt;h2 id="xname-本质上意味着什么">
x:Name 本质上意味着什么？
&lt;a href="#xname-%e6%9c%ac%e8%b4%a8%e4%b8%8a%e6%84%8f%e5%91%b3%e7%9d%80%e4%bb%80%e4%b9%88" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>那么，既然二者并没有多少区别，我们现在就来看一看 &lt;code>x:Name&lt;/code> 到底意味着什么。在 XAML 中，当我们给控件添加 &lt;code>x:Name&lt;/code> 属性时，实际上是在告诉 XAML 解析器，这个控件的名字是什么。并且相信大家都知道，拥有了名字的控件，它就会变成类的字段，我们可以在代码后台通过这个名字来访问它。&lt;/p>
&lt;p>具体来说，以 &lt;code>Window&lt;/code> 为例，我们会发现后台代码是一个分部类。在我们看不到的地方，XAML 解析器会生成一个类。这个类中就有我们最熟悉的在构造函数中调用的 &lt;code>InitializeComponent&lt;/code> 方法，以及我们在 XAML 中添加了 &lt;code>Name&lt;/code> 的控件所对应的字段。例如，我们在 XAML 中这样写：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="na">x:Class=&lt;/span>&lt;span class="s">&amp;#34;WpfApp1.MainWindow&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns=&lt;/span>&lt;span class="s">&amp;#34;http://schemas.microsoft.com/winfx/2006/xaml/presentation&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:x=&lt;/span>&lt;span class="s">&amp;#34;http://schemas.microsoft.com/winfx/2006/xaml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">Title=&lt;/span>&lt;span class="s">&amp;#34;MainWindow&amp;#34;&lt;/span> &lt;span class="na">Height=&lt;/span>&lt;span class="s">&amp;#34;450&amp;#34;&lt;/span> &lt;span class="na">Width=&lt;/span>&lt;span class="s">&amp;#34;800&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">x:Name=&lt;/span>&lt;span class="s">&amp;#34;button1&amp;#34;&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Click Me&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">x:Name=&lt;/span>&lt;span class="s">&amp;#34;button2&amp;#34;&lt;/span> &lt;span class="na">x:FieldModifier=&lt;/span>&lt;span class="s">&amp;#34;private&amp;#34;&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Click Me&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>那么，我们就能在后台生成的代码（文件名类似 &lt;code>MainWindow.g.i.cs&lt;/code>）中找到这样的内容（我们可以在后台随便一个地方访问这个字段，然后用 IDE 的跳转到定义的方式找到后台生成的代码）：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">partial&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MainWindow&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="c1">// ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">internal&lt;/span> &lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Windows&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Controls&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Button&lt;/span> &lt;span class="n">button1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Windows&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Controls&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Button&lt;/span> &lt;span class="n">button2&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>此外，如果 XAML 中的一个控件拥有了 &lt;code>Name&lt;/code>，我们还可以实现一些别的事情。包括但不限于：&lt;/p>
&lt;ol>
&lt;li>在 &lt;code>Binding&lt;/code> 表达式中使用 &lt;code>ElementName&lt;/code> 来绑定这个控件；&lt;/li>
&lt;li>在 &lt;code>Storyboard&lt;/code> 中使用 &lt;code>TargetName&lt;/code> 来指定这个控件；&lt;/li>
&lt;li>在后台代码中使用 &lt;code>FindName&lt;/code> 方法来查找这个控件。&lt;/li>
&lt;/ol>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>本文简单介绍了 WPF 中的 &lt;code>Name&lt;/code> 和 &lt;code>x:Name&lt;/code> 属性。虽然它们在本质上有一些区别，但在 WPF 中，它们的作用几乎是一样的。&lt;/p>
&lt;p>围绕着 &lt;code>Name&lt;/code> 这个概念，其实能聊的还有很多。比如：&lt;/p>
&lt;ol>
&lt;li>&lt;code>NameScope&lt;/code> 的概念；&lt;/li>
&lt;li>当持有 &lt;code>Name&lt;/code> 的控件在 &lt;code>ControlTemplate&lt;/code> 或 &lt;code>DataTemplate&lt;/code> 中时会怎样；&lt;/li>
&lt;li>与之相关的其他来自 &lt;code>x&lt;/code> 命名空间的属性，比如 &lt;code>x:FieldModifier&lt;/code>、&lt;code>x:Reference&lt;/code> 等。&lt;/li>
&lt;/ol>
&lt;p>这些内容，我们会在以后的文章中继续探讨。&lt;/p>
&lt;h2 id="参考">
参考
&lt;a href="#%e5%8f%82%e8%80%83" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;ul>
&lt;li>&lt;a class="link" href="https://stackoverflow.com/questions/589874/in-wpf-what-are-the-differences-between-the-xname-and-name-attributes" target="_blank" rel="noopener"
>In WPF, what are the differences between the x:Name and Name attributes?&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://learn.microsoft.com/en-us/dotnet/desktop/xaml-services/xname-directive" target="_blank" rel="noopener"
>x:Name Directive | Microsoft Learn&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>如何高效读取 XML 中所需的内容（其二）</title><link>https://blog.coldwind.top/posts/xml-read-benchmarks-2/</link><pubDate>Fri, 27 Sep 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/xml-read-benchmarks-2/</guid><description>&lt;img src="https://s2.loli.net/2024/10/11/XEIhj5DuRS6Wa4n.png" alt="Featured image of post 如何高效读取 XML 中所需的内容（其二）" />&lt;p>我们继续&lt;a class="link" href="https://blog.coldwind.top/posts/xml-read-benchmarks" >上一次的内容&lt;/a>，再来看一看关于 XML 内容读取有哪些意想不到的性能差别。这次我们用于演示的 XML 文本依旧是来自 W3Schools 的&lt;a class="link" href="https://www.w3schools.com/xml/simple.xml" target="_blank" rel="noopener"
>一个样例&lt;/a>，大致内容如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;breakfast_menu&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;food&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;name&amp;gt;&lt;/span>Belgian Waffles&lt;span class="nt">&amp;lt;/name&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;price&amp;gt;&lt;/span>$5.95&lt;span class="nt">&amp;lt;/price&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;description&amp;gt;&lt;/span>Two of our famous Belgian Waffles with plenty of real maple syrup&lt;span class="nt">&amp;lt;/description&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;calories&amp;gt;&lt;/span>650&lt;span class="nt">&amp;lt;/calories&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/food&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- 省略中间的三个 food --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;food&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;name&amp;gt;&lt;/span>Homestyle Breakfast&lt;span class="nt">&amp;lt;/name&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;price&amp;gt;&lt;/span>$6.95&lt;span class="nt">&amp;lt;/price&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;description&amp;gt;&lt;/span>Two eggs, bacon or sausage, toast, and our ever-popular hash browns&lt;span class="nt">&amp;lt;/description&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;calories&amp;gt;&lt;/span>950&lt;span class="nt">&amp;lt;/calories&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/food&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/breakfast_menu&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我们这次的任务是：获取最后一个 &lt;code>food&lt;/code> 的 &lt;code>calories&lt;/code> 的值（即 &lt;code>950&lt;/code>）。这次我们的选手有：LINQ to XML、&lt;code>XPath&lt;/code> 以及正则表达式。对于 &lt;code>XPath&lt;/code>，我们同样在 &lt;code>XDocument&lt;/code> 上进行操作（只需要引入 &lt;code>System.Xml.XPath&lt;/code> 命名空间即可）。&lt;/p>
&lt;h2 id="linq-to-xml">
LINQ to XML
&lt;a href="#linq-to-xml" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>我们先来看一看 LINQ to XML（即 &lt;code>System.Xml.Linq&lt;/code> 命名空间）该如何实现吧。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Elements&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">foods&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">doc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Root&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Elements&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;food&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">lastFood&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">foods&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Last&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">lastFood&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Element&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;calories&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>其实，这里因为我们很清楚 XML 文档的结构，所以上面的内容可以简化为：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Elements&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">doc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Root&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Elements&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">Last&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">Elements&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">Last&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样是可以提高一点性能的，因为我们不需要检查每个节点的名字。&lt;/p>
&lt;p>另外，我们还可以使用 &lt;code>Descendants&lt;/code> 这个方法，从而减少一些 &lt;code>Elements&lt;/code> 的调用。最极端的情况下，因为我们要获取的元素正好是最后一个，所以我们甚至别的什么都不用做，直接调用 &lt;code>Descendants&lt;/code> 就可以了：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Descendants&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">doc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Root&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Descendants&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">Last&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="xpath">
XPath
&lt;a href="#xpath" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>接下来我们看一看使用 XPath 表达式该如何实现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">XPath&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">doc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">XPathSelectElement&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;//food[last()]/calories&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里我们借助 XPath 表达式的特殊语法，直接选取了最后一个 &lt;code>food&lt;/code> 节点的 &lt;code>calories&lt;/code> 子节点。或者，因为我们知道总共五个 &lt;code>food&lt;/code> 节点，所以我们也可以将上面的 &lt;code>last()&lt;/code> 替换为 &lt;code>5&lt;/code>。这样确实会换来一点点提升，但是非常不明显，而且有耍赖的嫌疑，所以我们就不这么做了。&lt;/p>
&lt;p>上面的方式其实效率并不是最高的，因为 &lt;code>//food&lt;/code> 会搜索整个 XML 文档，寻找所有名称为 &lt;code>food&lt;/code> 的节点。如果我们能够将 XPath 表达式写得更加精确，是能够提升一些性能的：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">XPathOptimized&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">doc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">XPathSelectElement&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/breakfast_menu/food[last()]/calories&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样，我们就只需要搜索 &lt;code>breakfast_menu&lt;/code> 节点下的 &lt;code>food&lt;/code> 节点，而不是整个文档了。这个不经意的小改动，就能够带来显著的性能提升（约 4~5 倍！）。&lt;/p>
&lt;h2 id="正则表达式">
正则表达式
&lt;a href="#%e6%ad%a3%e5%88%99%e8%a1%a8%e8%be%be%e5%bc%8f" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>最后，我们再来看一看正则表达式的实现。这个实现方式就非常简单粗暴了。我们只需要匹配 &lt;code>calories&lt;/code> 节点，并拿到最后一个的值即可：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">Regex&lt;/span> &lt;span class="n">regex&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Regex&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">@&amp;#34;&amp;lt;calories&amp;gt;(\d+)&amp;lt;/calories&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Regex&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">matches&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">regex&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Matches&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">xml&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">matches&lt;/span>&lt;span class="p">[^&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="n">Groups&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="n">Value&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但其实我们仍然有相当大的优化空间。因为我们这里需要的是最后一个 &lt;code>calories&lt;/code> 节点，所以我们不需要匹配全部的 &lt;code>calories&lt;/code> 节点，只需要匹配到最后一个即可。实现这一操作的方式，除了修改表达式本身以外，我们还可以借助 &lt;code>RegexOptions.RightToLeft&lt;/code> 这个选项：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">Regex&lt;/span> &lt;span class="n">regex&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Regex&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">@&amp;#34;&amp;lt;calories&amp;gt;(\d+)&amp;lt;/calories&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">RegexOptions&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">RightToLeft&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">RegexOptimized&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">match&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">regex&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Match&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">xml&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">match&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Groups&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="n">Value&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>通过这样的一个简单操作，我们再次可以换来约 4~5 倍的性能提升。&lt;/p>
&lt;h2 id="性能对比">
性能对比
&lt;a href="#%e6%80%a7%e8%83%bd%e5%af%b9%e6%af%94" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>现在，我们可以来看一看比赛的结果了：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Method&lt;/th>
&lt;th style="text-align: right">Mean&lt;/th>
&lt;th style="text-align: right">Error&lt;/th>
&lt;th style="text-align: right">StdDev&lt;/th>
&lt;th style="text-align: right">Gen0&lt;/th>
&lt;th style="text-align: right">Gen1&lt;/th>
&lt;th style="text-align: right">Allocated&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Elements&lt;/td>
&lt;td style="text-align: right">101.3 ns&lt;/td>
&lt;td style="text-align: right">10.07 ns&lt;/td>
&lt;td style="text-align: right">0.55 ns&lt;/td>
&lt;td style="text-align: right">0.0101&lt;/td>
&lt;td style="text-align: right">0.0001&lt;/td>
&lt;td style="text-align: right">128 B&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Descendants&lt;/td>
&lt;td style="text-align: right">243.4 ns&lt;/td>
&lt;td style="text-align: right">26.84 ns&lt;/td>
&lt;td style="text-align: right">1.47 ns&lt;/td>
&lt;td style="text-align: right">0.0062&lt;/td>
&lt;td style="text-align: right">0.0005&lt;/td>
&lt;td style="text-align: right">80 B&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>RegexMatch&lt;/td>
&lt;td style="text-align: right">605.7 ns&lt;/td>
&lt;td style="text-align: right">158.79 ns&lt;/td>
&lt;td style="text-align: right">8.70 ns&lt;/td>
&lt;td style="text-align: right">0.1278&lt;/td>
&lt;td style="text-align: right">0.0010&lt;/td>
&lt;td style="text-align: right">1608 B&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>RegexMatchOptimized&lt;/td>
&lt;td style="text-align: right">128.1 ns&lt;/td>
&lt;td style="text-align: right">59.86 ns&lt;/td>
&lt;td style="text-align: right">3.28 ns&lt;/td>
&lt;td style="text-align: right">0.0305&lt;/td>
&lt;td style="text-align: right">0.0002&lt;/td>
&lt;td style="text-align: right">384 B&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>XPathOptimized&lt;/td>
&lt;td style="text-align: right">1,297.4 ns&lt;/td>
&lt;td style="text-align: right">927.55 ns&lt;/td>
&lt;td style="text-align: right">50.84 ns&lt;/td>
&lt;td style="text-align: right">0.3681&lt;/td>
&lt;td style="text-align: right">0.0038&lt;/td>
&lt;td style="text-align: right">4624 B&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>XPath&lt;/td>
&lt;td style="text-align: right">5,099.2 ns&lt;/td>
&lt;td style="text-align: right">1,591.05 ns&lt;/td>
&lt;td style="text-align: right">87.21 ns&lt;/td>
&lt;td style="text-align: right">0.8087&lt;/td>
&lt;td style="text-align: right">0.0076&lt;/td>
&lt;td style="text-align: right">10208 B&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>不知道这样的结果有没有出乎大家的意料呢？不难发现，看似不起眼的 LINQ to SQL 方法，居然能轻易击败了优化过的正则表达式以及 XPath，尤其是 XPath 的速度居然会这么慢，进入了微秒级别。&lt;/p>
&lt;p>另一方面，在上一次比赛中胜出的正则表达式，这次居然也不敌 LINQ to XML，尤其是如果不优化，那么正则表达式的性能还要再差上不少。&lt;/p>
&lt;p>所以，这次的跑分再次向我们证明，对于 XML 文档的读取，LINQ to XML 是最好的选择，可以说是不仅好用，而且高效。&lt;/p>
&lt;h2 id="one-more-thing">
One More Thing
&lt;a href="#one-more-thing" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>说到跑分，这种时候怎么少得了 &lt;code>Span&lt;/code> 呢？&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Span&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">xml&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Xml&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AsSpan&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">xml&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Slice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">xml&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">LastIndexOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;&amp;lt;calories&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="m">10&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">xml&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">LastIndexOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;&amp;lt;/calories&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">-&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">xml&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">LastIndexOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;&amp;lt;calories&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="m">10&lt;/span>&lt;span class="p">)));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>至于结果嘛：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Method&lt;/th>
&lt;th style="text-align: right">Mean&lt;/th>
&lt;th style="text-align: right">Error&lt;/th>
&lt;th style="text-align: right">StdDev&lt;/th>
&lt;th style="text-align: right">Ratio&lt;/th>
&lt;th style="text-align: right">RatioSD&lt;/th>
&lt;th style="text-align: right">Allocated&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Span&lt;/td>
&lt;td style="text-align: right">22.30 ns&lt;/td>
&lt;td style="text-align: right">0.435 ns&lt;/td>
&lt;td style="text-align: right">0.024 ns&lt;/td>
&lt;td style="text-align: right">0.22&lt;/td>
&lt;td style="text-align: right">0.00&lt;/td>
&lt;td style="text-align: right">-&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table></description></item><item><title>要认识、理解、重视信息差</title><link>https://blog.coldwind.top/posts/notice-understand-info-asymmetry/</link><pubDate>Mon, 23 Sep 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/notice-understand-info-asymmetry/</guid><description>&lt;p>在我们的学习与生活中，信息差可以说是无处不在。所谓“信息差”，其实就是因为不同的人的见识、经历、机会等的不同，而产生的一种所了解的信息的不平等的现象。这一现象将会导致掌握更多信息的一方在某些情况下具有显著的优势。有一个经典的寓言故事，“拧这颗螺丝值 1 美元，而知道要拧这颗螺丝值 999 美元”。这其实就是信息差。&lt;/p>
&lt;h2 id="认识信息差">
认识信息差
&lt;a href="#%e8%ae%a4%e8%af%86%e4%bf%a1%e6%81%af%e5%b7%ae" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>信息差是客观存在且几乎不可能消除的。韩愈在《师说》中提到，“闻道有先后，术业有专攻”。每个人去学习并了解一样事物的早晚和快慢都是不同的。有的人可能是相关经验比较丰富，或从小就对某样东西充满了兴趣和探索的欲望，又或者运气好，早早就找到了点醒自己的学习资料。也因此，他们早早地掌握了更多的信息，并且基于这些信息，又能够如同指数爆炸一样，快速接受更多的内容。这就是一种优势。&lt;/p>
&lt;p>比如最近大火的游戏《黑神话·悟空》，就会出现一种分歧：一部分人觉得，这游戏并不难啊，一些比较厉害的 BOSS 网上很容易找到攻略，直接“耍赖皮”就能过关；而也有一些不擅长动作类游戏，或者说不擅长各种游戏、只是因为游戏的热度和对于剧情的兴趣慕名而来的玩家们就会叫苦不迭，因为游戏还没有提供难度选择。不是所有人都会越挫越勇，屡战屡败本身就很可能消磨人的意志，最终产生负面情感。但前者就会觉得，你们就是菜、就是懒，明明网上大把的攻略，你们都懒得找。&lt;/p>
&lt;p>早些年深度学习很火的时候，国内有一些卖课的博主，他们只是把斯坦福大学的 CS231N 的课件拿过来就能放在知乎割大把的韭菜；电商平台时常会见到的“i7 级处理器”和只要九块九的 1T 容量的 U 盘，这种看似“明显”的骗局却屡屡有人中招；机圈人尽皆知的搭载骁龙 680 的电子垃圾，却有可能被人以 2000 元的价格买走……这样的例子数不胜数，但它们也终究是一直并将长期存在着。这些都是因为信息差导致的。&lt;/p>
&lt;h2 id="理解信息差">
理解信息差
&lt;a href="#%e7%90%86%e8%a7%a3%e4%bf%a1%e6%81%af%e5%b7%ae" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>但是我们仅仅认识到信息差的存在是不够的。重要的是，我们要理解信息差。为什么这么说呢？还是上面提到的《黑神话》的例子。或许你知道某个 BOSS 有一个很偷鸡很轻松的打法，但是你比那些“菜鸟”多知道的，其实远不仅仅是“有这个打法”。很有可能是，你在发现这篇攻略之前，已经在相关的社区和论坛沉浸了数个小时。或许这时在你看来，只要在搜索引擎里面简单敲几个字，就能再找到这一篇；可这对于一个完全不了解这方面信息的人来说，所欠缺的时间，可能是巨大的。&lt;/p>
&lt;p>在每个领域都是，就比如我和我的绝大多数读者们所处的 .NET 这一领域。或许有一个知识、一个概念、一个技巧对于你来说仿佛家常便饭，相应的博客文章、视频教程、官方文档你可以信手拈来；或许有一个问题，身经百战的你一看报错信息就知道是因为配错了环境，但是却可能让新手在百度上搜索了半天，最后找到了一篇机翻的 CSDN 文章才勉强解决问题；一个简单的接口、依赖注入，对于你来说早已经是轻车熟路、老生常谈，却依旧能够轻易地困惑一个非 CS 科班出身的新手数年。很多时候，人和人之间的差别并不是智商，而是信息。&lt;/p>
&lt;p>虽然我明白这个道理，但我也经常会不由自主地去产生不好的情感。比如我经常会在弹幕或评论区看到有人问主题配色和字体用的是什么，或者问一些我早就有相关视频探讨过的内容。明明我已经专门出了视频、在很多处评论给出了解答，还专门写了置顶专栏来解答常见问题，可就是有人不知道。但是换位思考，当我正在看油管上的 Tim Corey、Nick Chapsas、Brian Launas 这些大佬的视频的时候，除非我把他们的所有视频都大致刷了一遍，否则我依旧要花时间去搜索、去研究、去搞懂。我仍记得，第一次看 Brian 的视频时，他项目中使用了 Prism 框架，但是我并不知道他一直都是这样做的，所以第一次看到他的 MVVM 框架“莫名其妙”地自动实现了一些功能时完全一脸懵逼。而这一点，对于他的其他观众而言，简直可以说是“常识”——是的，每个人都有自己的“常识”，而且有可能和别人的交集占比并不大。&lt;/p>
&lt;p>也因此，每次我介绍 MVVM 相关的内容时，我会尽量多说两句 MVVM 社区工具包的原理，从而让之前没接触过的观众不至于搞不清楚那些看不到的属性是哪儿来的；看到问主题颜色和字体的问题时，我也时刻提醒自己，我知道我在很多地方提供了回答，也依旧是不够的——我现在已经有一百多期视频了，连我自己有时候都搞不清楚某个知识点我到底有没有讲过。甚至因为我用了包含连字符的字体，导致代码中有时候会出现诸如“≠”的符号，我现在都觉得有些不妥。这里给看我 MVVM 工具包视频并且因为这些符号感到困惑过的观众朋友们道个歉。&lt;/p>
&lt;h2 id="重视信息差">
重视信息差
&lt;a href="#%e9%87%8d%e8%a7%86%e4%bf%a1%e6%81%af%e5%b7%ae" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>前面我们说了，对于信息差，我们要认识、要理解，但同时，我们还要重视。什么是重视呢？我们要知道，虽然我们可能比其他人碰巧多知道了一些知识，从而使我们在某些时候占据了优势，但是我们也要时常反思。“吾日三省吾身”，因为信息差客观存在，别人有欠缺的信息，我们极有可能也有，而且可能还有不少。所以，谁也别瞧不起谁。每个人都有自己的盲区，都有自己还不熟悉的领域。在这个信息爆炸的时代，我们永远都不要只活在自己的舒适区，要敢于跳出自己的成见，去了解、去学习、去接受新的事物。&lt;/p>
&lt;p>人怎样才能进步呢？程序员的进步，不是靠着写相同的代码写 10000 遍，而是靠着每天都能写点之前从来没写过的代码，思考一些以前没想过或没相同的事情。每天都做自己认为对的事情，或许并没有进步；每天意识到曾经的自己犯过一些错误，这才是吐故纳新。要敢于承认自己的不足，正视自己的缺点，笑对别人的鄙夷。相信将来的自己，回过头来，会觉得一路走来，每一次“难受”都是值得的。&lt;/p>
&lt;p>除此之外，我们还有另外一个层面的“重视”——我们要警惕别人利用信息差来欺骗或蒙蔽自己。有的人掌握了信息差，或许只是妄自尊大、盲目自信、充满了优越感；但更有一部分人，会特意借助信息差来收“智商税”。这样的例子在我们的生活中可以说是无处不在，不管是网店、二手交易平台、网课平台，还是各种中介、招聘平台等等。我们一定要擦亮自己的眼睛。&lt;/p>
&lt;h2 id="结语">
结语
&lt;a href="#%e7%bb%93%e8%af%ad" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>最后，希望大家都能够理解信息差，要时刻怀着一种“自己还有很多不知道的东西”的态度，努力学习，尽力减少此类情形。另一方面，我们还要提高警惕，因为可能会有人借助信息差来欺骗我们。面对那些当下掌握了比自己少的信息的人，不要嗤之以鼻，而是友好相处，伸出援手。毕竟，把自己掌握的知识教会给别人，不正是践行了费曼学习法、证明自己确实掌握了这个知识吗？只有每个人都这样，我们才能建设一个良好的社区环境。&lt;/p></description></item><item><title>WPF 设计时特性的实用技巧</title><link>https://blog.coldwind.top/posts/wpf-design-time-attributes/</link><pubDate>Sat, 07 Sep 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/wpf-design-time-attributes/</guid><description>&lt;blockquote>
&lt;p>本文有对应的视频教程：&lt;a class="link" href="https://www.bilibili.com/video/BV17kptetEQV/" target="_blank" rel="noopener"
>哔哩哔哩&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>相信无数 WPF 开发者在开发过程中，都会遭受过很多这样或那样的痛苦，比如：&lt;/p>
&lt;ul>
&lt;li>在设计 &lt;code>TextBlock&lt;/code> 控件时，因为无法预览字体、字号、颜色等属性，导致需要临时给 &lt;code>Text&lt;/code> 属性赋一个值，查看效果后再删除；&lt;/li>
&lt;li>有一个默认折叠的 &lt;code>Expander&lt;/code> 控件，但是在设计时无法看到折叠后的效果，只能在运行时查看，或者临时修改 &lt;code>IsExpanded&lt;/code> 属性；&lt;/li>
&lt;li>&lt;code>Window&lt;/code> 的 &lt;code>DataContext&lt;/code> 因为在后台代码中赋值，导致在设计时无法看到绑定的数据，也无法在书写绑定时获得智能提示。&lt;/li>
&lt;/ul>
&lt;p>如果你有过这样的困扰，那么这篇文章一定可以帮助到你。本文将介绍 WPF 中设计时特性的使用方法，让你在设计时就能看到更多的效果，提高开发效率。&lt;/p>
&lt;h2 id="基本概念">
基本概念
&lt;a href="#%e5%9f%ba%e6%9c%ac%e6%a6%82%e5%bf%b5" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>设计时特性（Design-Time Attributes）是 WPF 中的一种特性，用于在设计时为控件提供更多的信息，以便在设计时能够更好地预览控件的效果。这一功能其实默认一直都是开启的，只是我想可能很多开发者都没有注意过。比如我们在 WPF 中新建一个 &lt;code>UserControl&lt;/code>，那么就会在 XAML 的开头看到类似这样的代码：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;UserControl&lt;/span> &lt;span class="na">x:Class=&lt;/span>&lt;span class="s">&amp;#34;WpfApp1.MyUserControl&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns=&lt;/span>&lt;span class="s">&amp;#34;http://schemas.microsoft.com/winfx/2006/xaml/presentation&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:x=&lt;/span>&lt;span class="s">&amp;#34;http://schemas.microsoft.com/winfx/2006/xaml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:mc=&lt;/span>&lt;span class="s">&amp;#34;http://schemas.openxmlformats.org/markup-compatibility/2006&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:d=&lt;/span>&lt;span class="s">&amp;#34;http://schemas.microsoft.com/expression/blend/2008&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">mc:Ignorable=&lt;/span>&lt;span class="s">&amp;#34;d&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">d:DesignHeight=&lt;/span>&lt;span class="s">&amp;#34;450&amp;#34;&lt;/span> &lt;span class="na">d:DesignWidth=&lt;/span>&lt;span class="s">&amp;#34;800&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/UserControl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在开头，模板自动为我们添加了很多 XML 命名空间（&lt;code>xmlns&lt;/code>），但是很多开发者可能只了解 &lt;code>xmlns&lt;/code> 与 &lt;code>xmlns:x&lt;/code> 这两个，而往往忽略了另外的几个。但其实另外的几个（&lt;code>xmlns:mc&lt;/code>、&lt;code>xmlns:d&lt;/code>）就提供了设计时特性的支持。最典型的例子就比如上面的&lt;code>d:DesignHeight&lt;/code>和&lt;code>d:DesignWidth&lt;/code>，这两个属性就是用来在设计时指定控件的高度和宽度的。这些特性最大的特点就是，它们只在设计时起作用，不会影响运行时的效果。&lt;/p>
&lt;p>了解了这些基本概念后，我们就可以开始介绍一些常用的设计时特性了。&lt;/p>
&lt;h2 id="常用设计时特性">
常用设计时特性
&lt;a href="#%e5%b8%b8%e7%94%a8%e8%ae%be%e8%ae%a1%e6%97%b6%e7%89%b9%e6%80%a7" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>比如 &lt;code>TextBlock&lt;/code> 的 &lt;code>Text&lt;/code> 在设计时并没有内容（比如是 &lt;code>DynamicResource&lt;/code> 从运行时加载的语言文件中获取，或绑定了 &lt;code>ViewModel&lt;/code> 中的属性，但是设计时这个属性没有值，却又想预览字体效果，这时候就可以使用 &lt;code>d:Text&lt;/code> 来指定设计时的文本内容：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;{DynamicResource ResourceKey=HelloWorld}&amp;#34;&lt;/span> &lt;span class="na">d:Text=&lt;/span>&lt;span class="s">&amp;#34;Hello, World!&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样在设计时就可以看到 &lt;code>Hello, World!&lt;/code> 这个文本了。&lt;/p>
&lt;p>如果 &lt;code>TextBlock&lt;/code> 的内容是用多个 &lt;code>Run&lt;/code> 组合而成，那么我们也可以用这样的方式来实现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;TextBlock&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;d:TextBlock.Inlines&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Run&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;Hello, &amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Run&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;World!&amp;#34;&lt;/span> &lt;span class="na">FontWeight=&lt;/span>&lt;span class="s">&amp;#34;Bold&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/d:TextBlock.Inlines&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/TextBlock&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&amp;lt;!-- 或者也可以用下面将要介绍的虚拟控件 --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;d:TextBlock&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Run&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;Hello, &amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;d:Run&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;World!&amp;#34;&lt;/span> &lt;span class="na">FontWeight=&lt;/span>&lt;span class="s">&amp;#34;Bold&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/d:TextBlock&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>其他类似的例子还比如：&lt;/p>
&lt;ol>
&lt;li>有一个只在特殊情况下才会展示的进度条，希望查看它的效果：&lt;code>d:Visibility=&amp;quot;Visible&amp;quot;&lt;/code>&lt;/li>
&lt;li>有一个平时默认折叠的面板，想查看效果：&lt;code>d:IsExpanded=&amp;quot;True&amp;quot;&lt;/code>&lt;/li>
&lt;li>一个用户控件，想给它一个相对合理的默认大小：&lt;code>d:DesignHeight=&amp;quot;600&amp;quot;&lt;/code> 或 &lt;code>d:Height&lt;/code>&lt;/li>
&lt;li>一个下拉框，想查看选中项的预览效果：&lt;code>d:SelectedIndex=&amp;quot;0&amp;quot;&lt;/code>&lt;/li>
&lt;li>一个导航用的 &lt;code>ContentControl&lt;/code>，我们希望预览导航到某一页的效果，就可以写 &lt;code>d:ContentControl.Content&lt;/code>&lt;/li>
&lt;/ol>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">关于上面的第 5 个例子，为什么我们直接将想要导航的内容以下面要提到的虚拟控件的方式添加到 &lt;code>ContentControl&lt;/code> 之中呢？因为通常来说，我们写的导航页面是借助 &lt;code>UserControl&lt;/code> 实现的，而它的命名空间通常为 &lt;code>xmlns:local&lt;/code> 等。对于这样的命名空间，我们没有办法使用 &lt;code>d:&lt;/code> 技巧，所以这里我们选择为 &lt;code>ContentControl&lt;/code> 的 &lt;code>Content&lt;/code> 属性添加 &lt;code>d:&lt;/code> 特性。&lt;/div>
&lt;/div>
&lt;h2 id="虚拟控件">
虚拟控件
&lt;a href="#%e8%99%9a%e6%8b%9f%e6%8e%a7%e4%bb%b6" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>我们不仅可以借助设计时特性来实现控件某些属性的虚拟，还可以虚拟整个控件出来：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;d:Button&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Virtual Button&amp;#34;&lt;/span> &lt;span class="na">Style=&lt;/span>&lt;span class="s">&amp;#34;{StaticResource MyButtonStyle}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样就可以在设计时看到一个虚拟的按钮了，而不需要在运行时才能看到效果。这一技巧可以用来预览按钮的样式。&lt;/p>
&lt;p>还有一种常见情形是，我们设计的软件会让用户去手动添加一些项目，从而动态生成对应的控件。对于这样的情况，我们如果能在设计时就看到一些“生成”出来的控件，那么就能更好地开发样式了。此时，我们添加一些虚拟控件，就可以满足这个需求。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ItemsControl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;d:ItemsControl.Items&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Button 1&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Button 2&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Button 3&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/d:ItemsControl.Items&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/ItemsControl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="设计时数据">
设计时数据
&lt;a href="#%e8%ae%be%e8%ae%a1%e6%97%b6%e6%95%b0%e6%8d%ae" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>这段内容可以说是重中之重了。很多开发者苦恼于因为在 Window 的代码后台通过 &lt;code>this.DataContext = new ViewModel();&lt;/code> 来添加 &lt;code>ViewModel&lt;/code>，导致在设计时无法看到绑定的数据，也无法获得智能提示。这时候我们可以使用 &lt;code>d:DataContext&lt;/code> 来指定设计时的数据：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="err">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">d:DataContext=&lt;/span>&lt;span class="s">&amp;#34;{d:DesignInstance Type=vm:MainViewModel}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>DesignInstance&lt;/code> 还有一个 &lt;code>IsDesignTimeCreatable&lt;/code> 属性，用于指定是否在设计时创建实例。如果设为 &lt;code>True&lt;/code>，还将能够在设计时看到一些 ViewModel 中属性的默认值。&lt;/p>
&lt;p>或者我们还可以这样写，并且还可以在 XAML 中定制一些 ViewModel 的属性的初始值，便于观察效果：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="err">...&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;d:DataContext&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;vm:MainViewModel&lt;/span> &lt;span class="na">Message=&lt;/span>&lt;span class="s">&amp;#34;Hello!&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/d:DataContext&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="列表项">
列表项
&lt;a href="#%e5%88%97%e8%a1%a8%e9%a1%b9" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>如果我们有一个 &lt;code>ListBox&lt;/code>，并且想要查看列表项的效果，可以这样：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ListBox&lt;/span> &lt;span class="na">ItemsSource=&lt;/span>&lt;span class="s">&amp;#34;{Binding Students}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;d:ListBox.ItemsSource&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;x:Array&lt;/span> &lt;span class="na">Type=&lt;/span>&lt;span class="s">&amp;#34;model:Student&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;model:Student&lt;/span> &lt;span class="na">Name=&lt;/span>&lt;span class="s">&amp;#34;Alice&amp;#34;&lt;/span> &lt;span class="na">Age=&lt;/span>&lt;span class="s">&amp;#34;18&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;model:Student&lt;/span> &lt;span class="na">Name=&lt;/span>&lt;span class="s">&amp;#34;Bob&amp;#34;&lt;/span> &lt;span class="na">Age=&lt;/span>&lt;span class="s">&amp;#34;19&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;model:Student&lt;/span> &lt;span class="na">Name=&lt;/span>&lt;span class="s">&amp;#34;Charlie&amp;#34;&lt;/span> &lt;span class="na">Age=&lt;/span>&lt;span class="s">&amp;#34;20&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/x:Array&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/d:ListBox.ItemsSource&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/ListBox&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>如果不想写 &lt;code>x:Array&lt;/code>，而是希望采用传统的为 &lt;code>Items&lt;/code> 添加控件的方式添加预览项，也可以这样：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ListBox&lt;/span> &lt;span class="na">ItemsSource=&lt;/span>&lt;span class="s">&amp;#34;{Binding Students}&amp;#34;&lt;/span> &lt;span class="na">d:ItemsSource=&lt;/span>&lt;span class="s">&amp;#34;{x:Null}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ListBox.Items&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ListBoxItem&amp;gt;&lt;/span>Student 1&lt;span class="nt">&amp;lt;/ListBoxItem&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ListBoxItem&amp;gt;&lt;/span>Student 2&lt;span class="nt">&amp;lt;/ListBoxItem&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ListBoxItem&amp;gt;&lt;/span>Student 3&lt;span class="nt">&amp;lt;/ListBoxItem&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ListBox.Items&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/ListBox&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里额外写一个 &lt;code>d:ItemsSource=&amp;quot;{x:Null}&amp;quot;&lt;/code> 是因为 &lt;code>ItemsSource&lt;/code> 和 &lt;code>Items&lt;/code> 两个属性不能同时使用，所以我们需要将 &lt;code>ItemsSource&lt;/code> 设置为 &lt;code>null&lt;/code>，就可以避免这个报错了。&lt;/p>
&lt;p>除了这些，如果我们想要预览的是比较简单的数据，或者我们并不非常关心数据的内容及格式，只是希望生成几个项目从而查看样式或模板的书写是否有问题，那么还有一个更简单的方法：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ListBox&lt;/span> &lt;span class="na">ItemsSource=&lt;/span>&lt;span class="s">&amp;#34;{Binding Students}&amp;#34;&lt;/span> &lt;span class="na">d:ItemsSource=&lt;/span>&lt;span class="s">&amp;#34;{d:SampleData ItemCount=5}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样就可以生成 5 个虚拟的列表项了。&lt;/p>
&lt;h2 id="更多功能">
更多功能
&lt;a href="#%e6%9b%b4%e5%a4%9a%e5%8a%9f%e8%83%bd" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>除了上面介绍的这些，还有很多其他的设计时特性，比如：&lt;/p>
&lt;ol>
&lt;li>&lt;code>d:DesignSource&lt;/code> 用于 &lt;code>CollectionViewSource&lt;/code> 的设计时数据；&lt;/li>
&lt;li>&lt;code>DesignData&lt;/code> 生成操作；&lt;/li>
&lt;li>在其他程序集的自定义控件及附加属性上使用设计时特性。&lt;/li>
&lt;/ol>
&lt;p>关于上面的这些内容，大家可以移步我的视频观看倒数两个章节。&lt;/p>
&lt;h2 id="参考">
参考
&lt;a href="#%e5%8f%82%e8%80%83" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;ul>
&lt;li>&lt;a class="link" href="https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/ee839627%28v=vs.100%29" target="_blank" rel="noopener"
>Design-Time Attributes&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/ee823176%28v=vs.100%29" target="_blank" rel="noopener"
>Using Sample Data in the WPF Designer&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://learn.microsoft.com/en-us/visualstudio/xaml-tools/xaml-designtime-data?view=vs-2022" target="_blank" rel="noopener"
>Use Design Time Data with the XAML Designer&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>如何高效读取 XML 中所需的内容（其一）</title><link>https://blog.coldwind.top/posts/xml-read-benchmarks/</link><pubDate>Sun, 18 Aug 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/xml-read-benchmarks/</guid><description>&lt;img src="https://s2.loli.net/2024/08/18/d1TnRLiG4kOu6j8.jpg" alt="Featured image of post 如何高效读取 XML 中所需的内容（其一）" />&lt;p>这次我们用于演示的 XML 文本来自 W3Schools 的&lt;a class="link" href="https://www.w3schools.com/xml/simple.xml" target="_blank" rel="noopener"
>一个样例&lt;/a>，内容如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;breakfast_menu&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;food&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;name&amp;gt;&lt;/span>Belgian Waffles&lt;span class="nt">&amp;lt;/name&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;price&amp;gt;&lt;/span>$5.95&lt;span class="nt">&amp;lt;/price&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;description&amp;gt;&lt;/span>Two of our famous Belgian Waffles with plenty of real maple syrup&lt;span class="nt">&amp;lt;/description&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;calories&amp;gt;&lt;/span>650&lt;span class="nt">&amp;lt;/calories&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/food&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- 省略中间的三个 food --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;food&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;name&amp;gt;&lt;/span>Homestyle Breakfast&lt;span class="nt">&amp;lt;/name&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;price&amp;gt;&lt;/span>$6.95&lt;span class="nt">&amp;lt;/price&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;description&amp;gt;&lt;/span>Two eggs, bacon or sausage, toast, and our ever-popular hash browns&lt;span class="nt">&amp;lt;/description&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;calories&amp;gt;&lt;/span>950&lt;span class="nt">&amp;lt;/calories&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/food&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/breakfast_menu&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>假定我们的任务是读取所有 &lt;code>food&lt;/code> 节点的 &lt;code>name&lt;/code> 属性的值，然后将它们存储到一个 &lt;code>List&amp;lt;string&amp;gt;&lt;/code> 中。首先我们来用几种方式来实现这个需求。&lt;/p>
&lt;h2 id="使用-xmldocument">
使用 XmlDocument
&lt;a href="#%e4%bd%bf%e7%94%a8-xmldocument" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>&lt;code>XmlDocument&lt;/code> 算是一种“传统”的方式。它有两种“玩法”，一种是使用诸如 &lt;code>GetElementsByTagName&lt;/code> 这样的方法，一点一点地找到我们需要的节点及其属性和内容；另一种是使用 XPath 表达式，一次性找到所有符合条件的节点。我们先来看看第一种方式：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">XmlDocument&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">doc&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">XmlDocument&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">doc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">LoadXml&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">testXml&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">doc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">GetElementsByTagName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;food&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">OfType&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">XmlNode&lt;/span>&lt;span class="p">&amp;gt;()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Select&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">node&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">node&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">]!.&lt;/span>&lt;span class="n">InnerText&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">ToList&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// var names = new List&amp;lt;string&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// foreach (XmlNode node in doc.GetElementsByTagName(&amp;#34;food&amp;#34;))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// {&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// names.Add(node[&amp;#34;name&amp;#34;]!.InnerText);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// }&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// return names;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">上面注释掉的代码是使用传统的 &lt;code>foreach&lt;/code> 循环来实现的，这样写在旧版本的 .NET 中可能会更快一些，但是在 .NET 7 以来的新版本中，LINQ 的性能已经得到了很大的提升。对于常见的集合类型，LINQ 的性能已经和传统的 &lt;code>foreach&lt;/code> 循环相差无几，甚至有时还更快，而且完全不会产生额外的 GC 压力。&lt;/div>
&lt;/div>
&lt;p>然后我们还可以用 XPath 表达式来实现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">XmlDocumentXPath&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">doc&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">XmlDocument&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">doc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">LoadXml&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">testXml&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">doc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">SelectNodes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;//food/name&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">OfType&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">XmlNode&lt;/span>&lt;span class="p">&amp;gt;()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Select&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">node&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">InnerText&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">ToList&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="使用-xmllinq">
使用 Xml.Linq
&lt;a href="#%e4%bd%bf%e7%94%a8-xmllinq" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>&lt;code>Xml.Linq&lt;/code> 是一种更加现代的方式，它的 API 设计更加友好，使用起来也更加方便。我们可以这样来实现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">XDocument&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">doc&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">XDocument&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">testXml&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">doc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Root&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Elements&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;food&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Select&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">node&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Element&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">)!.&lt;/span>&lt;span class="n">Value&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">ToList&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>XDocument&lt;/code> 同样可以使用 XPath 表达式来实现，但是这里我们就不演示了，因为 &lt;code>XDocument&lt;/code> 的 API 设计已经足够友好，不像是 &lt;code>XmlDocument&lt;/code> 那样使用 XPath 表达式会显得更加简洁。&lt;/p>
&lt;h2 id="使用-xmlreader">
使用 XmlReader
&lt;a href="#%e4%bd%bf%e7%94%a8-xmlreader" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>&lt;code>XmlReader&lt;/code> 是一种基于流的方式，它的操作并不简单，但是效率极高。我们可以这样来实现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">XmlReader&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">stringReader&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">StringReader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">testXml&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">xmlReader&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">System&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Xml&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">XmlReader&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">stringReader&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="m">8&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">xmlReader&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Read&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">xmlReader&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IsStartElement&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">xmlReader&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Name&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="s">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">xmlReader&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ReadElementContentAsString&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">res&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="使用-regex">
使用 Regex
&lt;a href="#%e4%bd%bf%e7%94%a8-regex" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>因为我们的任务过于简单，要解析的 XML 文本内容也很纯粹，所以我们还可以使用正则表达式来实现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">Regex&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">matches&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Regex&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Matches&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">testXml&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">@&amp;#34;&amp;lt;name&amp;gt;(.*?)&amp;lt;/name&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">matches&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Select&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">match&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">match&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Groups&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="n">Value&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">ToList&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="使用传统的字符串方法">
使用传统的字符串方法
&lt;a href="#%e4%bd%bf%e7%94%a8%e4%bc%a0%e7%bb%9f%e7%9a%84%e5%ad%97%e7%ac%a6%e4%b8%b2%e6%96%b9%e6%b3%95" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>最后，我们还可以使用传统的字符串方法来实现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">StringOps&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="m">8&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">cur&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 找到下一个 &amp;lt;name&amp;gt; 节点&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">idx&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">testXml&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IndexOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;&amp;lt;name&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cur&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 如果找不到，说明已经找完了&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">idx&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 找到对应的 &amp;lt;/name&amp;gt; 节点&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">end&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">testXml&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IndexOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;&amp;lt;/name&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">idx&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="m">6&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">testXml&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Substring&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">idx&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="m">6&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">end&lt;/span> &lt;span class="p">-&lt;/span> &lt;span class="n">idx&lt;/span> &lt;span class="p">-&lt;/span> &lt;span class="m">6&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 从下一个节点开始继续找&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cur&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">end&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="m">7&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>提前剧透一下，这个方式的效率非常低，远低于其他几种方式。因此，我们还有一个杀手锏：&lt;code>Span&amp;lt;T&amp;gt;&lt;/code>。&lt;/p>
&lt;h2 id="使用-spant">
使用 Span&lt;T>
&lt;a href="#%e4%bd%bf%e7%94%a8-spant" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>&lt;code>Span&amp;lt;T&amp;gt;&lt;/code> 是 C# 7.2 引入的一个新特性，它可以让我们更加高效地操作内存。我们可以这样来实现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">SpanOps&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="m">8&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">span&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">testXml&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AsSpan&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">idx&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">span&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IndexOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;&amp;lt;name&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">idx&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">end&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">span&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Slice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">idx&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="m">6&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">IndexOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;&amp;lt;/name&amp;gt;&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="n">idx&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="m">6&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">span&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Slice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">idx&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="m">6&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">end&lt;/span> &lt;span class="p">-&lt;/span> &lt;span class="n">idx&lt;/span> &lt;span class="p">-&lt;/span> &lt;span class="m">6&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">ToString&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">span&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">span&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Slice&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">end&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="m">7&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">res&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="性能测试">
性能测试
&lt;a href="#%e6%80%a7%e8%83%bd%e6%b5%8b%e8%af%95" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>现在我们就可以来测试一下这几种方式的性能了。我们使用 BenchmarkDotNet 来进行测试。结果如下：&lt;/p>
&lt;p>&lt;img src="https://s2.loli.net/2024/07/29/Cg3vUj1eVIFuA9z.png"
loading="lazy"
alt="Benchmark 结果"
>&lt;/p>
&lt;p>怎么样，大家领教了 &lt;code>Span&lt;/code> 的威力了吗？它一骑绝尘，已经进入纳秒的境界了。所以我们可以得出结论：&lt;/p>
&lt;ol>
&lt;li>如果我们要获取的内容并不复杂，我们完全可以使用正则表达式来抓取想要的内容，而不是死板地解析 XML 文档&lt;/li>
&lt;li>当较为复杂时，我们还是需要借助传统的方式进行读取。它们的性能关系为：&lt;code>XmlReader&lt;/code> &amp;gt; &lt;code>XDocument&lt;/code> &amp;gt; &lt;code>XmlDocument&lt;/code>&lt;/li>
&lt;li>从实用性的角度考虑， &lt;code>XDocument&lt;/code> 比 &lt;code>XmlReader&lt;/code> 及 &lt;code>XmlDocument&lt;/code> 都更加实用，速度比传统的 &lt;code>XmlDocument&lt;/code> 快，又并不显著逊于 &lt;code>XmlReader&lt;/code>，所以应该是我们在大多数情况下的最优选项&lt;/li>
&lt;li>使用 &lt;code>Span&lt;/code> 可以显著优化性能，尤其是我们需要频繁对字符串进行 &lt;code>IndexOf&lt;/code>、&lt;code>SubString&lt;/code> 等操作时&lt;/li>
&lt;/ol></description></item><item><title>使用 AsyncBarrier 来等待并同步多个异步任务</title><link>https://blog.coldwind.top/posts/use-asyncbarrier-to-sync-tasks/</link><pubDate>Sun, 11 Aug 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/use-asyncbarrier-to-sync-tasks/</guid><description>&lt;img src="https://s2.loli.net/2024/08/11/15IEZJX7fCq4caS.jpg" alt="Featured image of post 使用 AsyncBarrier 来等待并同步多个异步任务" />&lt;blockquote>
&lt;p>本文有对应的视频教程：&lt;a class="link" href="https://www.bilibili.com/video/BV1Gx4y1479f/" target="_blank" rel="noopener"
>哔哩哔哩&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>大家在做异步编程开发的时候，不知道是否会遇到这样的一种情形：&lt;/p>
&lt;p>有多个异步任务，这些任务之间没有依赖关系，但是我们需要等待所有任务都完成后再继续执行后续的操作。我们唯一知道的，就是这些任务的数量。&lt;/p>
&lt;p>举个例子：我们现在有三个 IO 相关的异步任务。这些任务的先后顺序是不确定的，并且这些任务也不必同时发起，但是我们需要等待这三个任务都完成后再继续执行后续的操作。&lt;/p>
&lt;p>对于最普通的等待多个异步任务，我们首先肯定会想到使用 &lt;code>Task.WhenAll&lt;/code> 方法。但是 &lt;code>Task.WhenAll&lt;/code> 现在并不能满足我们的需求，因为它需要能够立刻获取到所有任务的集合。并且因为我们希望在每个异步任务的中间某个环节去等待其他任务的完成，而并不是所有异步任务都会在同一时间点发起，所以这就产生了一个矛盾。&lt;/p>
&lt;p>这时候大家可能会想到另外一种更加简单粗暴的方式：我们创建一个局部字段 &lt;code>int count&lt;/code>，然后每个异步任务完成后，我们将 &lt;code>count&lt;/code> 自增。当 &lt;code>count&lt;/code> 的值等于我们预期的任务数量时，我们就可以继续执行后续的操作。这种方式虽然可以解决问题，但是实现起来比较繁琐，因为我们还需要考虑使用什么机制来控制这些异步任务在 &lt;code>count&lt;/code> 达到预期值时进行后续操作。最简单的方式无疑是使用轮询，但这显然是不够好的。聪明一些的方式是使用信号量，如 &lt;code>SemaphoreSlim&lt;/code>，或者其他库提供的 &lt;code>AsyncAutoResetEvent&lt;/code> 等。当然，我们还可以采用更加轻量的 TCS（&lt;code>TaskCompletionSource&lt;/code>）来实现。但即便思路已经有了，实际实现起来依旧非常复杂，因为我们还要考虑 &lt;code>count&lt;/code> 变量的线程安全、异常处理、取消任务等。&lt;/p>
&lt;h2 id="引入-asyncbarrier">
引入 AsyncBarrier
&lt;a href="#%e5%bc%95%e5%85%a5-asyncbarrier" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>这时候，&lt;code>AsyncBarrier&lt;/code> 就派上用场了。&lt;code>AsyncBarrier&lt;/code> 是一个非常轻量级的类，它可以帮助我们等待并同步多个异步任务。这个类是由 &lt;code>Microsoft.VisualStudio.Threading&lt;/code> 提供的，我们可以轻易地找到&lt;a class="link" href="https://github.com/microsoft/vs-threading/blob/main/src/Microsoft.VisualStudio.Threading/AsyncBarrier.cs" target="_blank" rel="noopener"
>它的源代码&lt;/a>。&lt;/p>
&lt;p>实际在使用时，我并不推荐大家去直接将 &lt;code>Microsoft.VisualStudio.Threading&lt;/code> 这个库引入到项目中，因为这个库本身是一个非常庞大的库，而且里面还包含了一些代码分析器（Code Analyzers），会给我们的项目添加一些恼人的“波浪线”。所以，一般情况下，我更推荐大家去使用 &lt;code>Nito.AsyncEx&lt;/code> 这个库。但是它又不包含 &lt;code>AsyncBarrier&lt;/code> 这个类，所以我们可以直接将 &lt;code>AsyncBarrier&lt;/code> 的源代码复制到我们的项目中，然后稍作修改即可。如果你不想麻烦，我也提供了一个开箱即用的版本，在 &lt;a class="link" href="https://gist.github.com/BYJRK/b1b893bb5660cea32326025f49116609" target="_blank" rel="noopener"
>GitHub Gist&lt;/a> 上。&lt;/p>
&lt;p>我们来简单理解一下它的源代码。这里我节选了一部分：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">AsyncBarrier&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">participantCount&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">Stack&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Waiter&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">waiters&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">AsyncBarrier&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">participants&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">participants&lt;/span> &lt;span class="p">&amp;lt;=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ArgumentOutOfRangeException&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">nameof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">participants&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">$&amp;#34;Argument {nameof(participants)} must be a positive number.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">participantCount&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">participants&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">waiters&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Stack&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Waiter&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="n">participants&lt;/span> &lt;span class="p">-&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">ValueTask&lt;/span> &lt;span class="n">SignalAndWait&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CancellationToken&lt;/span> &lt;span class="n">cancellationToken&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">lock&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">waiters&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">waiters&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Count&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="m">1&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">participantCount&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">waiters&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Count&lt;/span> &lt;span class="p">&amp;gt;&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Waiter&lt;/span> &lt;span class="n">waiter&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">waiters&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Pop&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">waiter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CompletionSource&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">TrySetResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">default&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">waiter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CancellationRegistration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Dispose&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ValueTask&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cancellationToken&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IsCancellationRequested&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">?&lt;/span> &lt;span class="n">Task&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FromCanceled&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cancellationToken&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">:&lt;/span> &lt;span class="n">Task&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CompletedTask&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">TaskCompletionSource&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">EmptyStruct&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">tcs&lt;/span> &lt;span class="p">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">TaskCreationOptions&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">RunContinuationsAsynchronously&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">CancellationTokenRegistration&lt;/span> &lt;span class="n">ctr&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">cancellationToken&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CanBeCanceled&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ctr&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">cancellationToken&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Register&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">static&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">tcs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ct&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="n">TaskCompletionSource&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">EmptyStruct&lt;/span>&lt;span class="p">&amp;gt;)&lt;/span>&lt;span class="n">tcs&lt;/span>&lt;span class="p">!).&lt;/span>&lt;span class="n">TrySetCanceled&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ct&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tcs&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ctr&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">default&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">waiters&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Push&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="n">Waiter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tcs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ctr&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ValueTask&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tcs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Task&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里还有另外两个类型 &lt;code>Waiter&lt;/code> 和 &lt;code>EmptyStruct&lt;/code>，这里由于篇幅的关系就不展示了。它们做的事情也非常简单，前者用于存储等待器的信息，后者则是一个空结构体，用于表示一个空的异步操作。它们并不是我们的重点，所以就不展开讨论了。&lt;/p>
&lt;p>我们不难观察到这么几点：&lt;/p>
&lt;ol>
&lt;li>它内部有一个 &lt;code>participantCount&lt;/code> 字段，表示参与者的数量；另外还有一个 &lt;code>Stack&lt;/code>，用来存储所有等待的参与者；&lt;/li>
&lt;li>它只有一个公开的方法 &lt;code>SignalAndWait&lt;/code>，表示调用者现在要进入等待状态。在这个方法中：
&lt;ul>
&lt;li>首先，它会判断当前等待的参与者数量是否等于预期的参与者数量。如果是，那么就将等待器逐个从 &lt;code>Stack&lt;/code> 中弹出并唤醒；&lt;/li>
&lt;li>如果不是，那么就创建一个新的 &lt;code>TaskCompletionSource&lt;/code>，并将其存入 &lt;code>Stack&lt;/code> 中，然后返回这个 &lt;code>TaskCompletionSource&lt;/code> 的 &lt;code>Task&lt;/code> 给参与者用于 &lt;code>await&lt;/code>。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>当所有参与者都到齐后，&lt;code>SignalAndWait&lt;/code> 方法会返回一个已完成的 &lt;code>ValueTask&lt;/code>，这时候所有参与者都可以继续执行后续的操作。&lt;/li>
&lt;/ol>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">这里其实还有一个小细节，就是 &lt;code>Stack&lt;/code> 的容量是 &lt;code>participantCount - 1&lt;/code>。这是因为我们并不需要将最后一个参与者也入栈。毕竟，当“倒数第一”到达终点时，我们就可以宣告比赛结束了。&lt;/div>
&lt;/div>
&lt;h2 id="使用-asyncbarrier">
使用 AsyncBarrier
&lt;a href="#%e4%bd%bf%e7%94%a8-asyncbarrier" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>现在我们就可以来用一用它了。我们这里借助 &lt;code>CommunityToolkit.Mvvm&lt;/code> 这个库来写一个视图模型（ViewModel），大致如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">partial&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MainViewModel&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">ObservableObject&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">ObservableCollection&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">Results&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="n">AsyncBarrier&lt;/span> &lt;span class="n">_asyncBarrier&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">3&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [RelayCommand]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">async&lt;/span> &lt;span class="n">Task&lt;/span> &lt;span class="n">FirstJobAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CancellationToken&lt;/span> &lt;span class="n">token&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">Task&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Delay&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1500&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">token&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Results&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;First job completed. Waiting for async barrier...&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">_asyncBarrier&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SignalAndWait&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">token&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Results&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;First job completed.&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [RelayCommand]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">async&lt;/span> &lt;span class="n">Task&lt;/span> &lt;span class="n">SecondJobAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CancellationToken&lt;/span> &lt;span class="n">token&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">Task&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Delay&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1500&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">token&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Results&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Second job completed. Waiting for async barrier...&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">_asyncBarrier&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SignalAndWait&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">token&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Results&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Second job completed.&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [RelayCommand]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">async&lt;/span> &lt;span class="n">Task&lt;/span> &lt;span class="n">ThirdJobAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CancellationToken&lt;/span> &lt;span class="n">token&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">Task&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Delay&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1500&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">token&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Results&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Third job completed. Waiting for async barrier...&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">_asyncBarrier&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SignalAndWait&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">token&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Results&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Third job completed.&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里我们定义了三个异步方法 &lt;code>FirstJobAsync&lt;/code>、&lt;code>SecondJobAsync&lt;/code> 和 &lt;code>ThirdJobAsync&lt;/code>，它们分别模拟了三个异步任务。这三个任务之间没有依赖关系，但是我们希望在它们都完成后再继续执行后续的操作。我们在类中声明了一个 &lt;code>AsyncBarrier&lt;/code> 字段，然后让这三个任务都调用它的 &lt;code>SignalAndWait&lt;/code> 方法，这样就可以保证这三个任务都完成后才会继续执行后续的操作。&lt;/p>
&lt;p>实际运行代码，我们可以发现确实达到了我们想要实现的效果。这三个按钮可以让用户以任意的顺序及时间间隔进行点击，并且每个任务接近完成的时候，都会进入等待状态。只有当所有任务都完成后，我们才会看到所有任务都已完成的提示。&lt;/p>
&lt;p>更棒的是，&lt;code>AsyncBarrier&lt;/code> 还可以重复使用。毕竟它底层只是一个 &lt;code>Stack&lt;/code>。我们在等待时会入栈，等待完成后会出栈，最终使它回归初始状态。这样我们就可以在界面中反复实验这一现象。&lt;/p>
&lt;h2 id="取消任务">
取消任务
&lt;a href="#%e5%8f%96%e6%b6%88%e4%bb%bb%e5%8a%a1" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>现在我们希望更进一步，为这些异步任务添加取消功能。那么，首先我们可以添加 &lt;code>InitAllJobs&lt;/code> 与 &lt;code>FinishJobs&lt;/code> 两个方法：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">private&lt;/span> &lt;span class="n">AsyncBarrier&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">_asyncBarrier&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[MemberNotNull(nameof(_asyncBarrier))]&lt;/span> &lt;span class="c1">// 提示编译器，这个方法会确保 _asyncBarrier 不为空&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">private&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">InitJobs&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">_asyncBarrier&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_asyncBarrier&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">AsyncBarrier&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">3&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Results&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Clear&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">private&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">FinishJobs&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">bool&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">_asyncBarrier&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_asyncBarrier&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">success&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Results&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;All jobs completed successfully.&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Results&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Jobs were canceled.&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这两个方法分别用于初始化任务与结束任务。在初始化任务时，我们会创建一个新的 &lt;code>AsyncBarrier&lt;/code> 实例，并清空 &lt;code>Results&lt;/code> 集合。在结束任务时，我们会将 &lt;code>AsyncBarrier&lt;/code> 实例置空，并根据是否成功完成任务来添加提示信息。&lt;/p>
&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">这其实也是我比较推荐的使用 &lt;code>AsyncBarrier&lt;/code> 的方式。虽然我们前面说了，它可以被重复使用。但是观察它的源代码会发现，它非常轻量，也不需要担心资源释放的问题，因为我们大可以每次使用的时候都实例化一个新的出来。毕竟这样还有一个好处，就是每次我们都可以根据实际情况去调整它的 &lt;code>participantCount&lt;/code>。&lt;/div>
&lt;/div>
&lt;p>接下来我们就可以在每个异步任务中添加取消逻辑。以 &lt;code>FirstJobAsync&lt;/code> 为例：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="na">[RelayCommand]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">async&lt;/span> &lt;span class="n">Task&lt;/span> &lt;span class="n">FirstJobAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CancellationToken&lt;/span> &lt;span class="n">token&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">InitJobs&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">Task&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Delay&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1200&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">token&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Results&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;First job completed. Waiting for async barrier...&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">_asyncBarrier&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SignalAndWait&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">token&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">FinishJobs&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">TaskCanceledException&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Results&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;First job was canceled.&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">FinishJobs&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里的大致思路是：&lt;/p>
&lt;ol>
&lt;li>首先会调用 &lt;code>InitJobs&lt;/code> 方法，初始化任务。这里每个异步方法都会尝试去初始化，但只有第一个（即 &lt;code>AsyncBarrier&lt;/code> 字段为空时）是有效的；&lt;/li>
&lt;li>在异步任务中使用 &lt;code>try-catch&lt;/code> 块，捕获 &lt;code>TaskCanceledException&lt;/code> 异常。因为如果我们想要取消任务，那么这个异步任务中的 &lt;code>Task.Delay&lt;/code> 以及 &lt;code>AsyncBarrier.SignalAndWait&lt;/code> 都会抛出这个异常；&lt;/li>
&lt;li>当异步任务完成时，会调用 &lt;code>FinishJobs&lt;/code> 方法，结束任务。并且这里类似 &lt;code>InitJobs&lt;/code>，只有第一个异步任务会有效调用。&lt;/li>
&lt;/ol>
&lt;p>然后，我们还需要一个 &lt;code>RelayCommand&lt;/code>，用来实现取消功能：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="na">[RelayCommand]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">private&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">CancelAllJobs&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">FirstJobCommand&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IsRunning&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">FirstJobCommand&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Cancel&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">SecondJobCommand&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IsRunning&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">SecondJobCommand&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Cancel&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">ThirdJobCommand&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IsRunning&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">ThirdJobCommand&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Cancel&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样我们就实现了想要的效果了。此时，我们在 XAML 中的代码如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="err">...&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Window.DataContext&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;local:MainViewModel&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Window.DataContext&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;DockPanel&lt;/span> &lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;DockPanel&lt;/span> &lt;span class="na">DockPanel.Dock=&lt;/span>&lt;span class="s">&amp;#34;Bottom&amp;#34;&lt;/span> &lt;span class="na">LastChildFill=&lt;/span>&lt;span class="s">&amp;#34;False&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Job1&amp;#34;&lt;/span> &lt;span class="na">Command=&lt;/span>&lt;span class="s">&amp;#34;{Binding FirstJobCommand}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Job2&amp;#34;&lt;/span> &lt;span class="na">Command=&lt;/span>&lt;span class="s">&amp;#34;{Binding SecondJobCommand}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Job3&amp;#34;&lt;/span> &lt;span class="na">Command=&lt;/span>&lt;span class="s">&amp;#34;{Binding ThirdJobCommand}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Cancel&amp;#34;&lt;/span> &lt;span class="na">Command=&lt;/span>&lt;span class="s">&amp;#34;{Binding CancelAllJobsCommand}&amp;#34;&lt;/span> &lt;span class="na">DockPanel.Dock=&lt;/span>&lt;span class="s">&amp;#34;Right&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/DockPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ListBox&lt;/span> &lt;span class="na">ItemsSource=&lt;/span>&lt;span class="s">&amp;#34;{Binding Results}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/DockPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>其实上面的故事还没有结束，因为实际运行后会发现，&lt;code>Cancel&lt;/code> 按钮在任何时候都是可用的。这是因为我们没有正确处理它的 ICommand 的 &lt;code>CanExecute&lt;/code> 方法。这里我就不展开讲了，我在视频中有详细讲解，大家可以在文章开头找到相应的视频链接。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>&lt;code>AsyncBarrier&lt;/code> 是一个非常轻量级的类，它可以帮助我们等待并同步多个异步任务。它的实现非常简单，但是却非常实用。我们可以在异步任务中使用它，来保证多个异步任务都完成后再继续执行后续的操作。同时，我们还可以在异步任务中添加取消逻辑，来保证任务的可靠性。&lt;/p>
&lt;p>大家如果有这样的需求，不妨去试一下这个类，相信一定可以帮上忙。不仅如此，我们还可以借此学习微软官方的源代码，了解一下它的实现细节。这对我们提升编程能力也是非常有帮助的。&lt;/p></description></item><item><title>C# 字符串操作实用技巧及新手易犯错误</title><link>https://blog.coldwind.top/posts/csharp-string-tips-tricks/</link><pubDate>Sat, 27 Jul 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/csharp-string-tips-tricks/</guid><description>&lt;img src="https://s2.loli.net/2024/07/29/nzM6Ya8AJDZNlhi.jpg" alt="Featured image of post C# 字符串操作实用技巧及新手易犯错误" />&lt;blockquote>
&lt;p>本文有对应的视频教程：&lt;a class="link" href="https://www.bilibili.com/video/BV1mx4y1x7JR/" target="_blank" rel="noopener"
>哔哩哔哩&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>C# 为字符串相关的操作提供了很多实用的类，比如：&lt;/p>
&lt;ul>
&lt;li>&lt;code>string&lt;/code>&lt;/li>
&lt;li>&lt;code>StringBuilder&lt;/code>&lt;/li>
&lt;li>&lt;code>Encoding&lt;/code>&lt;/li>
&lt;li>&lt;code>Regex&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>它们的功能相当强大，但这也导致了我们可能并不足够了解它，导致我们可能并不熟悉某些方法的重载，或者不知道某些方法的性能问题，最终导致我们的代码效率低下（而且我们还常常察觉不到）。这篇文章我将为大家介绍一些 C# 字符串操作的实用技巧和易犯错误，希望能帮助大家更好地使用字符串。&lt;/p>
&lt;h2 id="在可以使用字符的时候不要使用字符串">
在可以使用字符的时候不要使用字符串
&lt;a href="#%e5%9c%a8%e5%8f%af%e4%bb%a5%e4%bd%bf%e7%94%a8%e5%ad%97%e7%ac%a6%e7%9a%84%e6%97%b6%e5%80%99%e4%b8%8d%e8%a6%81%e4%bd%bf%e7%94%a8%e5%ad%97%e7%ac%a6%e4%b8%b2" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在 C# 中，声明一个字符与字符串，有一个很典型的区别，就是使用单引号和双引号。除此之外，它们二者也是区别很大的。字符串变量实际上是在堆上分配了一块内存空间，这个空间用来存储字符串的内容。而字符则是值类型，它存储在栈上，所以它的性能要比字符串要好很多。所以绝对不能把字符简单理解为长度为 1 的字符串，它们是完全不同的两种类型，效率也是很不相同的。&lt;/p>
&lt;p>在 C# 中使用 &lt;code>string&lt;/code> 类型的某些方法时，我们就有机会使用字符而不是字符串，比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">string&lt;/span> &lt;span class="n">str&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Hello, World!&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">StartsWith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sc">&amp;#39;H&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">EndsWith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sc">&amp;#39;!&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Contains&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sc">&amp;#39;o&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IndexOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sc">&amp;#39;o&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sc">&amp;#39;,&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这些方法都有重载，可以接受字符作为参数，这样我们就可以直接使用字符而不是字符串，这样可以提高代码的性能。&lt;/p>
&lt;p>为什么要这么做呢？除了上面提到的引用类型和值类型的区别以外，它们还有其他一些区别。以 &lt;code>Contains&lt;/code> 为例，大家可以想象一下这个方法在底层是如何实现的。比如底层可能会是一个二层循环，第一层循环遍历字符串的每一个字符，第二层循环则在匹配到第一个字符后，再遍历后面的字符，看是否和我们要查找的子字符串相同。&lt;/p>
&lt;p>为了保证算法的通用性，即便我们传入的字符串长度为 1，底层也会把它当做一个字符串来处理。这对应到 JIT 编译后的机器码，就会有一些额外的开销，比如判断循环的跳出条件，以及跳转等。而如果我们传入的是字符，那么底层就可以直接比较单个字符的值，这样就可以减少一些额外的开销。&lt;/p>
&lt;h2 id="使用方法的重载减少不必要的调用">
使用方法的重载，减少不必要的调用
&lt;a href="#%e4%bd%bf%e7%94%a8%e6%96%b9%e6%b3%95%e7%9a%84%e9%87%8d%e8%bd%bd%e5%87%8f%e5%b0%91%e4%b8%8d%e5%bf%85%e8%a6%81%e7%9a%84%e8%b0%83%e7%94%a8" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>字符串类的很多方法都包含了大量的重载。正确使用这些重载，有利于我们减少一些额外的调用，以及所造成的资源浪费。比如下面几个例子：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">string&lt;/span> &lt;span class="n">str&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34; Hello, World,, Good, Morning &amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 这里我们希望将上面的内容按照逗号分割，并去除空字符串&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 正确的做法是使用下面这个 Split 方法的重载&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">slices&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sc">&amp;#39;,&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">StringSplitOptions&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">RemoveEmptyEntries&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//假如我们不知道这个重载，我们可能会写出下面这样的代码&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">slices&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sc">&amp;#39;,&amp;#39;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">Where&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IsNullOrEmpty&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">)).&lt;/span>&lt;span class="n">ToArray&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 还比如我们希望去除每个字符串的前后空格&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 正确的做法是使用下面这个 Split 方法的重载&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">slices&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sc">&amp;#39;,&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">StringSplitOptions&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">TrimEntries&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 同样地，假如我们不知道这个重载，我们可能会写出下面这样的代码&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">slices&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sc">&amp;#39;,&amp;#39;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">Select&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Trim&lt;/span>&lt;span class="p">()).&lt;/span>&lt;span class="n">ToArray&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>每一次对字符串类型调用它的常见方法，都会产生额外的开销。&lt;/p>
&lt;p>我们再来看另外一个例子：比较两个字符串是否相同。如果我们想要忽略大小写进行比较，我们可能会写出这样的代码：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">s1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToLower&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="n">s2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToLower&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但是实际上，我们有效率显著高于上面这种方式的方法 &lt;code>Equals&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">s1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Equals&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">StringComparison&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OrdinalIgnoreCase&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>是的，这个方法也拥有一些重载。这样我们就可以避免创建两个新的字符串，以及额外的比较操作。&lt;/p>
&lt;h2 id="string-类的构造函数">
string 类的构造函数
&lt;a href="#string-%e7%b1%bb%e7%9a%84%e6%9e%84%e9%80%a0%e5%87%bd%e6%95%b0" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>相信大多数新手可能声明字符串的方式都是直接使用双引号，或者对其他字符串调用一些方法而得到，比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">string&lt;/span> &lt;span class="n">str&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Hello, World!&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">string&lt;/span> &lt;span class="n">str2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Substring&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">5&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">string&lt;/span> &lt;span class="n">str3&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;,&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34; &amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但实际上，&lt;code>string&lt;/code> 类还有一些构造函数，可以帮助我们更好地创建字符串。相信用过 Python 的都知道，如果我们想在控制台输出一个长度为 20 个等号的分隔符，通常我们的做法是：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;=&amp;#39;&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>其实在 C# 中，我们也可以实现类似的效果：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">string&lt;/span> &lt;span class="n">sep&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sc">&amp;#39;=&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">20&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sep&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>除此之外，如果我们有一个字符数组，我们也可以使用 &lt;code>string&lt;/code> 类的构造函数来创建字符串。这个技巧一般用不到，可一旦我们有了一个需要转为字符串的字符数组，这个方法就会显得非常有用。一个典型的例子是，如果我们想翻转一个字符串，那么在不借助 &lt;code>Span&lt;/code> 或 &lt;code>unsafe&lt;/code> 的情况下，效率最高的方式为：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">char&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">chars&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToCharArray&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Array&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Reverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chars&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">string&lt;/span> &lt;span class="n">reversed&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chars&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>如果不知道字符串的构造函数的用法，可能就会写出下面的代码了：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">string&lt;/span> &lt;span class="n">reversed&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Reverse&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>当然了，我们永远可以写出更加辣眼睛的代码，不是吗？&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// string reversed = new string(str.Reverse().ToArray());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">char&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">chars&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">str&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Select&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">c&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">ToArray&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">chars&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">chars&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Reverse&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">ToArray&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">string&lt;/span> &lt;span class="n">reversed&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">chars&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="与操作系统有关的一些方法">
与操作系统有关的一些方法
&lt;a href="#%e4%b8%8e%e6%93%8d%e4%bd%9c%e7%b3%bb%e7%bb%9f%e6%9c%89%e5%85%b3%e7%9a%84%e4%b8%80%e4%ba%9b%e6%96%b9%e6%b3%95" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>由于 Windows 与 Unix 系统的一些区别，导致了两个时不常会让我们感到痛苦的事情：换行符和路径分隔符。在 Windows 系统中，换行符为 &lt;code>\r\n&lt;/code>（CRLF），而在 Unix 系统中，换行符为 &lt;code>\n&lt;/code>（LF）。而路径分隔符在 Windows 系统中为 &lt;code>\&lt;/code>，而在 Unix 系统中为 &lt;code>/&lt;/code>。&lt;/p>
&lt;p>在面对这些问题时，我们其实是有一些技巧的。比如处理路径时，我们可以使用 &lt;code>Path&lt;/code> 类，它会根据当前操作系统的不同，返回不同的路径分隔符：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">folder&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;MyFolder&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">subfolder&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;MySubFolder/&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">filename&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;MyFile.txt&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">path&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Combine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">folder&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">subfolder&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">filename&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这个方法不仅可以帮助我们处理路径分隔符，还可以帮助我们处理路径的拼接，以及路径的规范化。比如上面的例子中，&lt;code>subfolder&lt;/code> 末尾多了一个 &lt;code>/&lt;/code>，但是 &lt;code>Path.Combine&lt;/code> 方法会自动帮我们去除这个多余的 &lt;code>/&lt;/code>。&lt;/p>
&lt;p>类似地，面对换行符的问题，我们可以使用 &lt;code>Environment.NewLine&lt;/code> 来获取当前操作系统的换行符。比如我们可以用下面的方式拼接一个多行字符串：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">lines&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Hello, World!&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Good, Morning!&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">text&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Environment&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">NewLine&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">lines&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">类似 &lt;code>Environment.NewLine&lt;/code> 这样的属性，我们还有 &lt;code>Path.DirectorySeparatorChar&lt;/code>、&lt;code>Path.PathSeparator&lt;/code> 等，它们都可以帮助我们处理一些与操作系统有关的问题。&lt;/div>
&lt;/div>
&lt;p>不仅如此，.NET 6 还为我们提供了一个新方法：&lt;code>ReplaceLineEndings&lt;/code>。这个方法可以帮助我们将字符串中的换行符统一为当前操作系统的换行符：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">text&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Hello, World!\r\nGood morning!\nGood night!&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 如果不传参，则默认将换行符替换为当前操作系统的换行符&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">normalized&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">text&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ReplaceLineEndings&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 如果传入参数，则将换行符替换为指定的换行符&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">normalized&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">text&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ReplaceLineEndings&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;\n&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">normalized&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">text&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ReplaceLineEndings&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;\t&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>其实很多时候，我们根本不需要显式地与换行符打交道。因为 .NET 的很多方法都会自动帮我们处理这些问题，比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">lines&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">File&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ReadAllLines&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;file.txt&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">File&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteAllLines&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;file.txt&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">lines&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;...&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 在控制台输出文本，并自动换行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ReadLine&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 读取用户输入，并自动处理换行符&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">sb&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">StringBuilder&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AppendLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Hello, World!&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>等等。这些方法的名称中都会包含 &lt;code>Line&lt;/code> 这个单词，大加可以多多留意。&lt;/p>
&lt;h2 id="stringbuilder-的一些技巧">
StringBuilder 的一些技巧
&lt;a href="#stringbuilder-%e7%9a%84%e4%b8%80%e4%ba%9b%e6%8a%80%e5%b7%a7" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>&lt;code>StringBuilder&lt;/code> 可能是一个对于大家来说，既熟悉又陌生的类。熟悉是因为我们在处理大量字符串拼接时，都会用到它，陌生是因为我们可能并不了解它的所有功能。这里我就不多赘述了，我用一小段代码来展示 &lt;code>StringBuilder&lt;/code> 的一些技巧：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">sb&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">StringBuilder&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 添加字符串&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Hello, World!&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AppendLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Hello, World!&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AppendFormat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Hello, {0}!&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;World&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sc">&amp;#39;H&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">5&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 添加 5 个 &amp;#39;H&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Insert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Hello, &amp;#34;&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 在指定位置插入字符串&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Hello&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Good&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 替换字符串&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Remove&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">5&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 删除指定位置的字符串&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Clear&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 清空 StringBuilder&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToString&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 将 StringBuilder 转为字符串&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">5&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 将 StringBuilder 的一部分转为字符串&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>没想到吧，连它的 &lt;code>ToString&lt;/code> 方法都包含一个类似 &lt;code>SubString&lt;/code> 的重载，方便我们减少一次不必要的内存开销。&lt;/p>
&lt;h2 id="拥抱语法糖使用字符串内插">
拥抱语法糖，使用字符串内插
&lt;a href="#%e6%8b%a5%e6%8a%b1%e8%af%ad%e6%b3%95%e7%b3%96%e4%bd%bf%e7%94%a8%e5%ad%97%e7%ac%a6%e4%b8%b2%e5%86%85%e6%8f%92" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在 C# 6 中，我们迎来了字符串内插（String interpolation）这个语法糖。这个语法糖可以帮助我们更加方便地拼接字符串，而且还可以在字符串中插入表达式。比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;World&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">age&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">18&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">str&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">$&amp;#34;Hello, {name}! You are {age} years old.&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 在以前，我们可能会写出下面这样的代码&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">str&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Hello, {0}! You are {1} years old.&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">age&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>实际上，这个语法糖的作用远不止于此，它的性能是高于 &lt;code>string.Format&lt;/code> 的。甚至因为它性能的提升，我们在使用 &lt;code>StringBuilder&lt;/code> 时，都可以考虑使用字符串内插来代替 &lt;code>AppendFormat&lt;/code>。不过，对于这种情形，性能最高的方式是连续使用 &lt;code>Append&lt;/code> 方法，形如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">id&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">123&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;World&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">age&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">18&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">sb&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">StringBuilder&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 使用 AppendFormat&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AppendFormat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;ID: {0}, Name: {1}, Age: {2}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">age&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 使用字符串内插&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">$&amp;#34;ID: {id}, Name: {name}, Age: {age}&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 使用连续的 Append 方法&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">sb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;ID: &amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">Append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">Append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;, Name: &amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">Append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">Append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;, Age: &amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">Append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">age&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>除此之外，字符串还有很多技巧，比如：&lt;/p>
&lt;ol>
&lt;li>原始字符串（Raw string）&lt;/li>
&lt;li>&lt;code>StringPool&lt;/code> 与 &lt;code>string.Intern&lt;/code>&lt;/li>
&lt;li>&lt;code>Span&amp;lt;char&amp;gt;&lt;/code>&lt;/li>
&lt;li>文本编码（&lt;code>Encoding&lt;/code>）&lt;/li>
&lt;li>一些与字符串有关的特性&lt;/li>
&lt;/ol>
&lt;p>但是因为篇幅的关系，我们这次就不展开了。希望大家能够通过这篇文章，了解到一些 C# 字符串操作的实用技巧和易犯错误。希望大家在以后的开发中，能够更加熟练地使用字符串，写出更加高效的代码。&lt;/p></description></item><item><title>如何读写 INI 配置文件？</title><link>https://blog.coldwind.top/posts/deal-with-ini-file/</link><pubDate>Thu, 11 Jul 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/deal-with-ini-file/</guid><description>&lt;img src="https://s2.loli.net/2024/07/11/QyFiMdrfNPKpazT.jpg" alt="Featured image of post 如何读写 INI 配置文件？" />&lt;p>INI 文件是一种相当古老的配置文件格式，但很“可惜”的是，它如今依旧被广泛使用。正因为如此，即便现在已经出现了很多更加现代化的配置文件格式（比如 JSON、YAML、TOML 等），我们仍然可能会遇到读写 INI 文件的情形。那么这次我们就来看看如何在 C# 中读写 INI 文件。&lt;/p>
&lt;h2 id="ini-文件格式概述">
INI 文件格式概述
&lt;a href="#ini-%e6%96%87%e4%bb%b6%e6%a0%bc%e5%bc%8f%e6%a6%82%e8%bf%b0" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>INI 文件是一种文本文件，它由一系列的节（Section）和键值对（Key-value pair）组成。每个键值对都位于某个节中，键和值位于等号（&lt;code>=&lt;/code>）左右，而节则由方括号（&lt;code>[]&lt;/code>）括起来。一个简单的 INI 文件看起来可能是这样的：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="k">[Section1]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">Key1&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">Value1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">Key2&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">Value2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">[Section2]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">Key3&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">Value3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">Key4&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">Value4&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>此外，INI 文件还支持注释，通常以分号（&lt;code>;&lt;/code>）开头（有时也可以自定义为其他符号，比如 &lt;code>#&lt;/code> 等），直到行尾为止：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="k">[Section1]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">Key1&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">Value1 ; 这是一个注释&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>INI 文件的格式要求基本就是这样了。其他可能还有一些诸如命名习惯，以及等号左右是否添加空格等细节，但这些通常都是没有具体约束的。&lt;/p>
&lt;p>不难发现，INI 因为格式极其简单，所以它的解析也是相当容易的，我们稍加思考，通常就可以写出一个简单的解析器。但是，既然已经有现成的解析库，我们当然不必自己重复造轮子。接下来我们就来看看在 C# 中如何读写 INI 文件。&lt;/p>
&lt;h2 id="传统方法使用-win32-api">
传统方法：使用 Win32 API
&lt;a href="#%e4%bc%a0%e7%bb%9f%e6%96%b9%e6%b3%95%e4%bd%bf%e7%94%a8-win32-api" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>相信大家只要在网上搜索过这个问题，就一定会看到有人推荐使用 Win32 API 来读写 INI 文件。这种方法的优点是简单、高效，但缺点也很明显：它是不跨平台的。如果你的程序需要在 Linux 或 macOS 上运行，那么这种方法就不适用了。&lt;/p>
&lt;p>INI 文件的读写操作在 Windows 平台上有专门的 API 支持，这些 API 位于 &lt;code>kernel32.dll&lt;/code> 中。我们可以通过 P/Invoke 的方式调用这些 API，实现对 INI 文件的读写。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Runtime.InteropServices&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[DllImport(&amp;#34;kernel32&amp;#34;)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">private&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kd">extern&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">WritePrivateProfileString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">section&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">val&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">filePath&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[DllImport(&amp;#34;kernel32&amp;#34;)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">private&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kd">extern&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">GetPrivateProfileString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">section&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">def&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">StringBuilder&lt;/span> &lt;span class="n">retVal&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">size&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">filePath&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">IniReadValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">Section&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">Key&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">StringBuilder&lt;/span> &lt;span class="n">temp&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">StringBuilder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">256&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">GetPrivateProfileString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Section&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">Key&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">temp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">256&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">temp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToString&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我们只需要从 &lt;code>kernel32.dll&lt;/code> 中导入 &lt;code>WritePrivateProfileString&lt;/code> 和 &lt;code>GetPrivateProfileString&lt;/code> 两个函数，就可以实现对于 INI 文件的读写操作了。通常情况下，我们还会额外写一个 &lt;code>IniReadValue&lt;/code> 方法来包装 &lt;code>GetPrivateProfileString&lt;/code> 函数，以便更加方便地读取键值。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">除了上面的两个方法，&lt;code>kernel32.dll&lt;/code> 中还有一些其他的函数，比如 &lt;code>GetPrivateProfileSection&lt;/code>、&lt;code>GetPrivateProfileSectionNames&lt;/code> 等，它们可以帮助我们更加方便地操作 INI 文件。有兴趣的读者可以自行查阅相关文档。&lt;/div>
&lt;/div>
&lt;p>但是大家在使用这个库的时候，不知道会不会有一种仿佛“高射炮打蚊子”一样的心情？毕竟 INI 这么简单的一种格式，居然要使用到 P/Invoke 来调用 Win32 API，这未免也太麻烦了。所以，这种传统方式存在以下问题：&lt;/p>
&lt;ol>
&lt;li>使用体验差：需要通过 P/Invoke 来调用 Win32 API，这对于 C# 开发者来说并不是一种友好的体验。&lt;/li>
&lt;li>不跨平台：这种方法只能在 Windows 平台上使用，无法在 Linux 或 macOS 上运行。&lt;/li>
&lt;li>线程不安全：由于这是一个全局函数，每次调用都会操作外部文件，所以在多线程环境下可能会出现问题。&lt;/li>
&lt;li>时间复杂度高：假如我们想要读取 INI 文件中的多个键值对，那么就需要多次调用 &lt;code>GetPrivateProfileString&lt;/code> 函数，而每次调用都需要从文件开头开始读取，直到找到对应的键值对。这样的时间复杂度显然是不够理想的。&lt;/li>
&lt;li>文本编码：这种方法只会使用系统默认的文本编码（比如中文操作系统的 ANSI 对应 GBK 编码），无法指定其他编码，因此非 ASCII 字符可能会出现乱码。&lt;/li>
&lt;/ol>
&lt;p>所以下面我们再介绍几个别的库。但是大家也不要高兴太早，因为这些库虽然各有优点，但也有各自的问题。&lt;/p>
&lt;h2 id="第三方库ini-parser">
第三方库：Ini-Parser
&lt;a href="#%e7%ac%ac%e4%b8%89%e6%96%b9%e5%ba%93ini-parser" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>&lt;a class="link" href="https://github.com/rickyah/ini-parser" target="_blank" rel="noopener"
>ini-parser&lt;/a> 是一款非常好用的 INI 文件解析库。它可以一次性将整个 INI 文件解析为一个 &lt;code>IniData&lt;/code> 对象（可以想象成一个字典），从而方便我们像操作字典那样便捷又高效地进行高频率的读写操作，并在最后统一写回文件。&lt;/p>
&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">大家在 NuGet 中搜索 &lt;code>ini-parser&lt;/code> 时，还会发现 &lt;code>ini-parser-netstandard&lt;/code> 这个库。这两个库的功能是一样的，只是前者是 .NET Framework的，而后者则是 .NET Standard 2.0 的，因此可以在 .NET Core、.NET 5+ 及跨平台环境中使用，甚至还可以用于 Unity 游戏开发。推荐大家在任何情况下都使用后者。&lt;/div>
&lt;/div>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">IniParser&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">parser&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">FileIniDataParser&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">IniData&lt;/span> &lt;span class="n">data&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">parser&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ReadFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;config.ini&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 读取键值对&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">string&lt;/span> &lt;span class="n">value1&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s">&amp;#34;Section1&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s">&amp;#34;Key1&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">string&lt;/span> &lt;span class="n">value2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s">&amp;#34;Section1&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s">&amp;#34;Key2&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 修改键值对&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s">&amp;#34;Section1&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s">&amp;#34;Key1&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;NewValue1&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s">&amp;#34;Section1&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s">&amp;#34;Key2&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;NewValue2&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 写回文件&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">parser&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;config.ini&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>除此之外，它还支持更多功能，比如合并多个 INI 文件等。大家可以查看官方文档来了解更多信息。&lt;/p>
&lt;p>但是，这个库有一个非常明显的限制：虽然它一次性读取了整个 INI 文件，使得我们在需要频繁读写时更加高效。但是当文件较大时，一次性读取整个文件可能会导致占用更大的内存；不仅如此，如果我们的需求仅仅是临时读写某一项配置，那么这种一次性读取整个文件的方式显然是不够高效的。&lt;/p>
&lt;p>所以这里再和大家推荐另外一个库。&lt;/p>
&lt;h2 id="第三方库inisharp">
第三方库：IniSharp
&lt;a href="#%e7%ac%ac%e4%b8%89%e6%96%b9%e5%ba%93inisharp" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>&lt;a class="link" href="https://github.com/kevinlae/IniSharp" target="_blank" rel="noopener"
>IniSharp&lt;/a> 真是一个不错的名字。这个库提供了便捷的操作 INI 文件的方法，并且不依赖 Win32 API，因此可以在跨平台环境下使用。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">IniSharp&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">ini&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">IniFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;config.ini&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Encoding&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">UTF8&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">value1&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">ini&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Section1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Key1&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">value2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">ini&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Section1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Key2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Default&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 如果该键不存在，则创建并返回默认值&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">ini&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SetValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Section1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Key1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;NewValue1&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">ini&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">DeleteKey&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Section1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Key2&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">ini&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">DeleteSection&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Section2&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">sections&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">ini&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetSections&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">keys&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">ini&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetKeys&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Section1&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这个库并不会一次性读取整个 INI 文件，而是在每次操作时进行读取或写入操作，因此不会占用过多的内存。这在我们的需求是临时读写某一项配置时显得尤为重要。&lt;/p>
&lt;p>但是我仍然要泼大家一盆冷水：这个库的性能并不高，因为它底层的代码存在一些值得优化的空间。以读取单个键值的方法为例（以下代码为节选，并不完整）：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 它在底层声明了一个“碰巧”与 Win 32 API 一样的函数名&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">private&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">GetPrivateProfileString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">section&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">defaultValue&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">lines&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">File&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ReadAllLines&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filePath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">FileEncoding&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">ToList&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">sectionNum&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">keyNum&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">FindSectionAndKey&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">section&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">lines&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">sectionNum&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">keyNum&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">startIndex&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">lines&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">keyNum&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="n">IndexOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">equalsIndex&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">lines&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">keyNum&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="n">IndexOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sc">&amp;#39;=&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">startIndex&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="n">key&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">string&lt;/span> &lt;span class="n">strLalue&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">lines&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">keyNum&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="n">Substring&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">equalsIndex&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">hashIndex&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">strLalue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IndexOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">commentChar&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">hashIndex&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">?&lt;/span> &lt;span class="n">strLalue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Substring&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hashIndex&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">strLalue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">defaultValue&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">sectionNum&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">keyNum&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">lines&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Insert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sectionNum&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">$&amp;#34;{key}={defaultValue}&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">lock&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">lockObject&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">File&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteAllLines&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filePath&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">lines&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">FileEncoding&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">lock&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">lockObject&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">StreamWriter&lt;/span> &lt;span class="n">sw&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">File&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AppendText&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filePath&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sw&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">$&amp;#34;[{section}]&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sw&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">$&amp;#34;{key}={defaultValue}&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">defaultValue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>不难看出几个问题：&lt;/p>
&lt;ol>
&lt;li>它使用的是 &lt;code>ReadAllLines()&lt;/code> 方法，而不是采用 &lt;code>ReadLines()&lt;/code> 方法返回一个延迟加载的 &lt;code>IEnumerable&amp;lt;string&amp;gt;&lt;/code>，或使用 &lt;code>StreamReader&lt;/code> 逐行读取。这样会导致一次性读取整个文件，占用更多内存。尤其是即便我们要找的键就在文件的开头，它也会从头读取整个文件，这显然是不够高效的。&lt;/li>
&lt;li>它使用了 &lt;code>File.WriteAllLines()&lt;/code> 方法，每次修改都会重写整个文件。&lt;/li>
&lt;li>在插入新键值对时，它使用了 &lt;code>List.Insert()&lt;/code> 方法，这会导致整个列表的元素向后移动，时间复杂度为 O(n)。&lt;/li>
&lt;/ol>
&lt;p>除此之外，这个库还有其他一些提升空间，比如可以使用 &lt;code>Span&lt;/code>、&lt;code>ArrayPool&lt;/code> 等，来减少内存分配和 GC 压力。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>上面提到的几种方式可以说是各有千秋，总会存在一些缺憾，因此关于 INI 这么简单的一个文件格式，我们的故事并没有结束。在后续的文章中，我还会和大家分享更多关于 INI 文件的读写方法，以及一些优化技巧。欢迎大家继续关注我的博客。&lt;/p></description></item><item><title>为什么我们一般不使用公共字段，而是选择自动属性？</title><link>https://blog.coldwind.top/posts/why-we-prefer-property-over-field/</link><pubDate>Thu, 13 Jun 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/why-we-prefer-property-over-field/</guid><description>&lt;img src="https://s2.loli.net/2024/06/13/6rLfG3dzciJpjvO.jpg" alt="Featured image of post 为什么我们一般不使用公共字段，而是选择自动属性？" />&lt;blockquote>
&lt;p>本文有对应的视频教程：&lt;a class="link" href="https://www.bilibili.com/video/BV1ci421v7Uc/" target="_blank" rel="noopener"
>哔哩哔哩&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>在写 C# 代码的时候，我们经常会写诸如此类的自动属性：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="n">Person&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Age&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这时候相信很多人都会有疑问：为什么我们要费劲写成这样的自动属性？为什么不能直接简单地把它写成一个公共字段呢？就比如这样：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="n">Person&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Age&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>其实这是有一些原因的。我们这次就来探讨一下。&lt;/p>
&lt;h2 id="看待属性与字段的方式不同">
看待属性与字段的方式不同
&lt;a href="#%e7%9c%8b%e5%be%85%e5%b1%9e%e6%80%a7%e4%b8%8e%e5%ad%97%e6%ae%b5%e7%9a%84%e6%96%b9%e5%bc%8f%e4%b8%8d%e5%90%8c" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>首先最重要的，就是我们看待属性与字段的方式，或者对于它们所扮演的角色的理解是不一样的。&lt;/p>
&lt;p>当我们看到一个属性时，通常我们都会期望它拥有一个公共的读权限，同时拥有一个可能不公开的写权限还可能在初始化上存在一些限制。&lt;strong>通常我们写一个属性时，都是希望它存在被外界访问的价值，并且我们也充分考虑了后果&lt;/strong>（比如我们可以在 setter 中添加逻辑，或干脆不开放 setter）。&lt;/p>
&lt;p>也就是说，通常情况下我们希望一个属性它是一个：&lt;/p>
&lt;ol>
&lt;li>（一般情况下）可以在类外被访问到，并且具有一定的意义，是开发者故意暴露出来的成员&lt;/li>
&lt;li>它的初始化可能包含一些逻辑，比如可以在什么时候被初始化，是否必须被初始化，初始化后还能否更改等&lt;/li>
&lt;li>它后台未必一定对应一个字段，而是会通过一些方式来得到它的值&lt;/li>
&lt;/ol>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">&lt;p>对于第 3 条，我可以举出一些例子：&lt;/p>
&lt;ol>
&lt;li>&lt;code>List.Count&lt;/code> 实际与底层的 &lt;code>Array&lt;/code> 的长度有关&lt;/li>
&lt;li>&lt;code>AsyncRelayCommand.IsRunning&lt;/code> 与底层的 &lt;code>Task&lt;/code> 的状态有关&lt;/li>
&lt;li>&lt;code>ObservableValidator.HasErrors&lt;/code> 与底层用于存放错误信息的列表有关&lt;/li>
&lt;li>&lt;code>CheckBox.IsChecked&lt;/code> 与底层的依赖属性有关&lt;/li>
&lt;/ol>&lt;/div>
&lt;/div>
&lt;p>但是当我们看到字段时，通常会怎么考虑呢？我们先来看一段简单的代码：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Manager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">_uniqueId&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 一个可能有特殊作用的唯一 ID&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">IConfiguration&lt;/span> &lt;span class="n">_config&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 一个通过依赖注入的方式在构造中初始化的接口对象&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">_flag&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 一个只用于内部方法间传递状态的标志位&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">_syncRoot&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// 一个只用于类内部的线程锁&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>大家对于字段的印象是否一般都是这样的呢？如果是的话，那么相信在看到下面的代码时，一定会有点恍惚和不知所措吧：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Manager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">protected&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">UniqueId&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">Flag&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">ErrorMessage&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Oops!&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>所以这里面的道理相信大家应该已经有一定感觉了。是的，我们通常对于字段所扮演角色的理解是：&lt;/p>
&lt;ol>
&lt;li>它通常只用于类内，作为其他属性或方法的辅助角色（比如线程锁、标志位、依赖注入的对象等）&lt;/li>
&lt;li>它通常不包含太多的逻辑，只是一个简单的值，而且也不如属性那样具有多种初始化的方式&lt;/li>
&lt;li>它通常不太“安全”，或者说开发者在不了解的情况下不太敢轻易去操作它&lt;/li>
&lt;/ol>
&lt;p>基于这样不同的看待方式，相信大家应该都能理解为什么我们一般不直接使用公共字段了。&lt;/p>
&lt;p>当然了，例外情况肯定也是有的。比如说我们在开发一个简单的 Unity 游戏，那么通常我们会写出这样的代码：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Player&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">MonoBehaviour&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [SerializeField]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">health&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// Unity 官方推荐的命名习惯是首字母小写&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">attack&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">defense&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>或者当我们想要与 C/C++ 写的 DLL 交互时，我们可能会写出这样的代码：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="na">[StructLayout(LayoutKind.Sequential)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="nc">MyData&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">ushort&lt;/span> &lt;span class="n">Index&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">uint&lt;/span> &lt;span class="n">Value&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">byte&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">Data&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这些情况下，我们可能会直接使用公共字段，而不是属性。&lt;/p>
&lt;h2 id="长期的约定俗成">
长期的约定俗成
&lt;a href="#%e9%95%bf%e6%9c%9f%e7%9a%84%e7%ba%a6%e5%ae%9a%e4%bf%97%e6%88%90" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>既然我们有这样不同的看待方式，所以就出现了相当多类似的开发习惯，甚至连标准库及第三方库也在有意无意贯彻着这样的习惯。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">当然了，这里面其实还有一个“先有鸡还是先有蛋”的问题。也就是说，我们是因为有了这样的习惯，所以才会有这样的标准库设计，还是因为标准库设计的如此，所以我们才会有这样的习惯呢？不过这个问题就不在我们的讨论范围内了。&lt;/div>
&lt;/div>
&lt;p>这里我可以举很多例子：&lt;/p>
&lt;ol>
&lt;li>在 WPF 开发中，如果你想在 XAML 中绑定一个类（通常为 Model 或 ViewModel）的变量，那么这个变量必须是一个属性，而不能是一个字段。此外，WPF 中另一个相当重要的功能——依赖属性——也会充分和属性打交道。&lt;/li>
&lt;li>在进行类的序列化与反序列化时，Json.NET、System.Text.Json 等库默认只会序列化属性，而不会序列化字段。&lt;/li>
&lt;li>&lt;code>DataGrid&lt;/code>、&lt;code>PropertyGrid&lt;/code> 等会根据数据类型来自动生成界面的控件都是关注属性而非字段。&lt;/li>
&lt;li>在 EntityFramework Core 中，如果你想要使用代码优先（Code-First）的方式，那么你的实体类中的属性必须是属性，而不能是字段；而使用数据库优先（Database-First）的方式时，工具自动生成的也是属性。&lt;/li>
&lt;li>C# 的接口可以包含属性，但不能包含字段。&lt;/li>
&lt;li>C# 的记录类（record）底层也是使用属性来实现的。&lt;/li>
&lt;/ol>
&lt;p>其他还有一些别的例子，比如我们在使用数据映射的工具（如 Mapster、AutoMapper 等）时，可能也会发现属性和字段的一些不同之处。&lt;/p>
&lt;p>所以，既然这样的习惯广泛存在，我们为什么要选择做一个另类的开发者呢？&lt;/p>
&lt;h2 id="灵活性与封装性">
灵活性与封装性
&lt;a href="#%e7%81%b5%e6%b4%bb%e6%80%a7%e4%b8%8e%e5%b0%81%e8%a3%85%e6%80%a7" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>属性具有无与伦比的灵活性。我们可以在属性的 getter 和 setter 中添加任意的逻辑，比如数据校验：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Person&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">_age&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Age&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">get&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">_age&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">set&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ArgumentOutOfRangeException&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">nameof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="s">&amp;#34;Age must be greater than 0.&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_age&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>再比如通知功能：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ViewModel&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">INotifyPropertyChanged&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">_name&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">get&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">_name&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">set&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">_name&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">OnPropertyChanged&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">nameof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Name&lt;/span>&lt;span class="p">));&lt;/span> &lt;span class="c1">// 事件与方法的实现略&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但更重要的是它的封装性。比如常见的 setter 就有这么几种：&lt;/p>
&lt;ol>
&lt;li>&lt;code>public&lt;/code>：公共的 setter，任何人都可以修改这个属性&lt;/li>
&lt;li>&lt;code>protected&lt;/code>：受保护的 setter，只有继承这个类的子类才能修改这个属性&lt;/li>
&lt;li>&lt;code>private&lt;/code>：私有的 setter，只有这个类内部的方法才能修改这个属性&lt;/li>
&lt;li>&lt;code>internal&lt;/code>：内部的 setter，只有同一个程序集内的方法才能修改这个属性&lt;/li>
&lt;li>&lt;code>init&lt;/code>：初始化 setter，只能在构造函数中初始化这个属性&lt;/li>
&lt;li>空：只读属性，只能在构造函数中初始化这个属性&lt;/li>
&lt;/ol>
&lt;p>不仅如此，还可以配合诸如 &lt;code>required&lt;/code>、&lt;code>virtual&lt;/code> 等关键字，使得属性的灵活性和封装性更上一层楼。这些都是字段完全无法比拟的（我知道上面的一些关键字也可以用于字段，但效果都很有限，比如会同时限制读写的权限等）。&lt;/p>
&lt;h2 id="性能方面的考虑">
性能方面的考虑
&lt;a href="#%e6%80%a7%e8%83%bd%e6%96%b9%e9%9d%a2%e7%9a%84%e8%80%83%e8%99%91" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>这时候可能有同学又要说了：我知道自动属性其实是个语法糖，最终还是会被编译器转换成字段和方法，形如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Person&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [CompilerGenerated]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Age&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="n">k__BackingField&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Age&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [CompilerGenerated]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">get&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Age&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="n">k__BackingField&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [CompilerGenerated]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">set&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Age&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="n">k__BackingField&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>那么调用方法去读写字段的值，效率上理应比直接读写字段要低对吧？如果是这样的话，把 &lt;code>{ get; set; }&lt;/code> 这样的自动属性直接写成公共字段，不是更好吗？&lt;/p>
&lt;p>这是个好问题，我们来看这样一个例子。下面的 &lt;code>Person&lt;/code> 类中，我们定义了两个属性，一个是自动属性，一个是字段：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Person&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Age1&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">10&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Age2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">20&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Person&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Age1&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Age2&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>如果我们观察 IL 代码，会发现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">IL_0000: newobj instance void Person::.ctor()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">IL_0005: dup
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">IL_0006: ldc.i4.s 10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">IL_0008: callvirt instance void Person::set_Age1(int32)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">IL_000d: ldc.i4.s 20
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">IL_000f: stfld int32 Person::Age2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">IL_0014: ret
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>好像确实不大对劲啊。&lt;code>Age1&lt;/code> 就是使用了 &lt;code>Person::set_Age1&lt;/code> 方法，而 &lt;code>Age2&lt;/code> 却直接使用了 &lt;code>stfld&lt;/code> 指令。那是不是说明修改属性的速度就是会略微慢于直接修改字段呢？先别急，我们再来看一看 JIT 编译后的代码：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Program.&amp;lt;Main&amp;gt;$(System.String[])
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> L0000: mov ecx, 0x33a4ca1c
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> L0005: call 0x066f300c
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> L000a: mov dword ptr [eax+4], 0xa
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> L0011: mov dword ptr [eax+8], 0x14
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> L0018: ret
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里我们可以看到，自动属性的 setter 其实会被 JIT 编译器优化成直接的内存写入操作。这就意味着，实际上在运行时，修改属性和直接修改字段的速度是一样的。所以，自动属性的性能和公共字段是完全一样的。大家大可以打消这个顾虑了。&lt;/p>
&lt;h2 id="net-9-即将到来的新语法特性">
.NET 9 即将到来的新语法特性
&lt;a href="#net-9-%e5%8d%b3%e5%b0%86%e5%88%b0%e6%9d%a5%e7%9a%84%e6%96%b0%e8%af%ad%e6%b3%95%e7%89%b9%e6%80%a7" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>如果你还在犹豫的话，我还可以再告诉你一个好消息：.NET 9（C# 13）即将引入一个新语法特性：&lt;code>field&lt;/code> 关键字（这个关键字曾经在 C# 11 的时候就释放过信号，但因为一些原因姗姗来迟）。这个新特性可以让你更加方便地声明一个属性。&lt;/p>
&lt;p>我们都知道，以前我们写完整属性（&lt;code>propfull&lt;/code>）时，需要写成这样：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">private&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">_age&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Age&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">get&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">_age&lt;/span> &lt;span class="p">/&lt;/span> &lt;span class="m">2&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">set&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">_age&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">value&lt;/span> &lt;span class="p">*&lt;/span> &lt;span class="m">2&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但是现在，有了 &lt;code>field&lt;/code> 关键字，我们可以这样写：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Age&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">get&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">field&lt;/span> &lt;span class="p">/&lt;/span> &lt;span class="m">2&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">set&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">field&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">value&lt;/span> &lt;span class="p">*&lt;/span> &lt;span class="m">2&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里的 &lt;code>field&lt;/code> 就相当于那个 &lt;code>_age&lt;/code> 字段。这样一来，我们就可以更加方便地声明一个属性了。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>通过上面的讨论，相信大家对于为什么我们一般不使用公共字段，而是选择自动属性有了更深的理解。当然了，这并不是说我们就不能使用公共字段了。在一些特殊的场景下，我们还是可以使用公共字段的。但是在大多数情况下，我们还是应该选择自动属性。&lt;/p>
&lt;p>C# 后面不断新增的语法特性，一直在优化我们使用属性的体验。在比较旧的 C# 版本中，我们甚至不能给自动属性直接赋值，而是需要通过构造函数来初始化。但是随着 C# 版本的不断更新，我们可以看到，自动属性的使用变得越来越方便了。除了上面提到的即将到来地 &lt;code>field&lt;/code> 关键字，我们在 C# 9 还迎来了记录类型，在 C# 12 又迎来了主构造函数。这些都是为了让我们更加方便地使用属性。&lt;/p>
&lt;p>相信大家今后可以更加无忧无虑地使用属性。&lt;/p></description></item><item><title>如何在 C# 中模拟 Go 的 defer 关键字并用于客户端开发</title><link>https://blog.coldwind.top/posts/mimic-go-defer-in-csharp/</link><pubDate>Tue, 28 May 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/mimic-go-defer-in-csharp/</guid><description>&lt;img src="https://s2.loli.net/2024/05/28/WyuKtqiXZQ3pDEA.jpg" alt="Featured image of post 如何在 C# 中模拟 Go 的 defer 关键字并用于客户端开发" />&lt;blockquote>
&lt;p>本文有对应的视频教程：&lt;a class="link" href="https://www.bilibili.com/video/BV1Ym421T7CS/" target="_blank" rel="noopener"
>哔哩哔哩&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;h2 id="go-中的-defer-大概是怎么一回事">
Go 中的 defer 大概是怎么一回事
&lt;a href="#go-%e4%b8%ad%e7%9a%84-defer-%e5%a4%a7%e6%a6%82%e6%98%af%e6%80%8e%e4%b9%88%e4%b8%80%e5%9b%9e%e4%ba%8b" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>Go 语言中有一个非常好用的 &lt;code>defer&lt;/code> 关键字。&lt;code>defer&lt;/code> 会在函数返回之前执行，可以用来释放资源，关闭文件等。比如我们想打开并读取一个外部文件的内容，我们可以这样写：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">ReadFile&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">file&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;file.txt&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 如果打开文件失败，直接返回
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 在函数返回之前关闭文件
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">defer&lt;/span> &lt;span class="nx">file&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 读取文件内容
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">content&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="kt">byte&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1024&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">file&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Read&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">string&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">content&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在这个例子中，我们使用 &lt;code>defer&lt;/code> 关键字来确保在函数返回之前关闭文件。这样我们就不用担心忘记关闭文件，导致资源泄漏。&lt;/p>
&lt;p>其实在 C# 和 Python 中，我们也可以借助一些特殊的语法来实现类似的效果。比如在 C# 中，我们可以使用 &lt;code>using&lt;/code> 关键字来确保资源在使用完之后被释放；在 Python 中，我们可以使用 &lt;code>with&lt;/code> 关键字来确保资源在使用完之后被释放。&lt;/p>
&lt;p>但有些时候，我们想要实现的功能只是希望在离开作用域之前执行一些代码，而不是释放资源。这种情况下，我们仍然可以借助 &lt;code>using&lt;/code> 关键字来实现，可以为我们带来意想不到的便利。&lt;/p>
&lt;h2 id="在-c-中模拟-go-的-defer">
在 C# 中模拟 Go 的 defer
&lt;a href="#%e5%9c%a8-c-%e4%b8%ad%e6%a8%a1%e6%8b%9f-go-%e7%9a%84-defer" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>前面已经提到，我们需要在 C# 中使用 &lt;code>using&lt;/code> 关键字来模拟 Go 的 &lt;code>defer&lt;/code>。但是 &lt;code>using&lt;/code> 关键字只能用于“释放资源”，或者说需要对一个实现了 &lt;code>IDisposable&lt;/code> 接口的对象进行操作。那么我们就必须实现 &lt;code>Dispose&lt;/code> 相关的逻辑了。话虽如此，并没有人规定我们必须在 &lt;code>Dispose&lt;/code> 方法中执行释放资源的逻辑。比如我们前面提到的，希望在离开作用域之前执行一些代码，就可以放在 &lt;code>Dispose&lt;/code> 方法中去执行。&lt;/p>
&lt;p>基于这个思路，我们可以写出这样的代码：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MyDisposable&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">IDisposable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">Action&lt;/span> &lt;span class="n">_callback&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">MyDisposable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Action&lt;/span> &lt;span class="n">callback&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">_callback&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">callback&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Dispose&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">_callback&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样我们就可以在 &lt;code>Dispose&lt;/code> 方法中执行我们想要执行的代码了。比如我们可以这样使用：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">void&lt;/span> &lt;span class="n">Foo&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">md&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MyDisposable&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Job is done.&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Do something&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我们在上面的例子中还用到了 C# 8.0 的新特性：&lt;code>using&lt;/code> 语法的改进。在 C# 8.0 中，我们可以省略 &lt;code>using&lt;/code> 语句中的大括号，直接在 &lt;code>using&lt;/code> 语句后面写一个表达式。这样我们就可以更加简洁地使用 &lt;code>using&lt;/code> 语法了。&lt;/p>
&lt;p>它实际对应的底层 C# 代码是这样的：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">void&lt;/span> &lt;span class="n">Foo&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">MyDisposable&lt;/span> &lt;span class="n">md&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MyDisposable&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Job is done.&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Do something&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">finally&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">md&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">md&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Dispose&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>所以可以保证 &lt;code>finally&lt;/code> 语句中的代码一定会被执行，即使在 &lt;code>try&lt;/code> 语句中抛出了异常。&lt;/p>
&lt;h2 id="这一技巧在-wpf-开发中的妙用">
这一技巧在 WPF 开发中的妙用
&lt;a href="#%e8%bf%99%e4%b8%80%e6%8a%80%e5%b7%a7%e5%9c%a8-wpf-%e5%bc%80%e5%8f%91%e4%b8%ad%e7%9a%84%e5%a6%99%e7%94%a8" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>其实这个小妙招并不是我的原创，而是油管上的 &lt;a class="link" href="https://www.youtube.com/@jason-williams" target="_blank" rel="noopener"
>Jason Williams&lt;/a> 在他的&lt;a class="link" href="https://www.youtube.com/watch?v=DOtS7IOtACI" target="_blank" rel="noopener"
>一期视频&lt;/a>中提到的。在他的视频中，他为我们提供了一个绝妙的点子。&lt;/p>
&lt;p>我们在做 WPF（以及其他诸如 Win UI、Avalonia 等）的客户端开发时，经常会遇到一个问题，就是需要去管理一个进度条的可见状态。比如我们现在有一个异步任务，我们希望任务在执行期间能够显示一个进度条，任务执行完毕（不管成功与否）后进度条消失。通常我们的做法是：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 模拟搜索电影的异步任务&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">IsBusy&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 控制进度条是否可见，且该属性具备通知功能&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">async&lt;/span> &lt;span class="n">Task&lt;/span> &lt;span class="n">SearchMovieAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">movieName&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">IsBusy&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(!&lt;/span>&lt;span class="n">CanSearch&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">IsBusy&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">resList&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">SearchMoviesFromInternetAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">movieName&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">resList&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="p">||&lt;/span> &lt;span class="n">resList&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Count&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">IsBusy&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">resList&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Do something&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">IsBusy&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>可以看到，我们在方法中需要多次根据情况设置 &lt;code>IsBusy&lt;/code> 属性。这样的代码看起来不太优雅。为了解决这个问题，我们就可以用上前面实现的类了。不过我们需要稍微修改一下，使它的回调函数可以接受一个参数：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">BusyDisposable&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">IDisposable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">Action&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">bool&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_busySetter&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">BusyDisposable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Action&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">bool&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">busySetter&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_busySetter&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">busySetter&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_busySetter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Dispose&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">_busySetter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后我们就可以这样使用了：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">async&lt;/span> &lt;span class="n">Task&lt;/span> &lt;span class="n">SearchMovieAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">movieName&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">BusyDisposable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">IsBusy&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(!&lt;/span>&lt;span class="n">CanSearch&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">resList&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">SearchMoviesFromInternetAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">movieName&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">resList&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="p">||&lt;/span> &lt;span class="n">resList&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Count&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">resList&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Do something&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">&lt;p>这里有一个需要注意的点：我们是在 ViewModel 中对 &lt;code>IsBusy&lt;/code> 进行的操作，并借助绑定来控制前台进度条的显示。&lt;strong>这无形中帮助我们解决了一个重要的隐患：线程安全&lt;/strong>。即便我们在非 UI 线程中修改了 &lt;code>IsBusy&lt;/code> 属性，由于 WPF 的数据绑定机制，我们也不用担心线程安全问题。&lt;/p>
&lt;p>但如果是在 View 中去直接操作进度条的 &lt;code>Visibility&lt;/code> 属性，那么就可能需要我们自己去处理线程安全问题了。常见的方式比如使用 &lt;code>Dispatcher&lt;/code>，或参考我的这篇 &lt;a class="link" href="https://blog.coldwind.top/posts/how-to-report-progress" >关于使用 IProgress 的文章&lt;/a>。&lt;/p>&lt;/div>
&lt;/div>
&lt;p>相信大家立刻就能够明白这个方式有多么简洁和优雅了。我们通过使用 &lt;code>using&lt;/code> 关键字，保证了当前作用域中的代码不管是正常执行还是异常退出，都会在离开作用域之前执行 &lt;code>IsBusy = false&lt;/code> 这一行代码。这样我们就不用在方法中多次设置 &lt;code>IsBusy&lt;/code> 属性了。&lt;/p>
&lt;p>甚至我们还能再稍微优化一下，比如使用一个自动属性来简化 &lt;code>BusyDisposable&lt;/code> 的实例化：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">private&lt;/span> &lt;span class="n">BusyDisposable&lt;/span> &lt;span class="n">NewBusyDisposable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">BusyDisposable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">IsBusy&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">async&lt;/span> &lt;span class="n">Task&lt;/span> &lt;span class="n">SearchMovieAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">movieName&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">NewBusyDisposable&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样我们就可以进一步简化这一语法，从而使其更接近 Go 语言中的 &lt;code>defer&lt;/code> 关键字的使用方式。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>本期内容主要介绍了 Go 语言中的 &lt;code>defer&lt;/code> 关键字，以及如何在 C# 中模拟 &lt;code>defer&lt;/code> 的实现。虽然我们似乎一定程度上“滥用”了 &lt;code>using&lt;/code> 关键字以及 &lt;code>IDisposable&lt;/code> 接口，但这种方式确实可以带来一些意想不到的便利。&lt;/p>
&lt;p>油管上的这位 Jason Williams 也绝对是一位大神。虽然他视频非常少，粉丝也只有几百，但是每期内容都堪称精品。大家有机会的话也可以去关注一下他，相信一定会有所收获。&lt;/p></description></item><item><title>如何在异步任务中汇报进度</title><link>https://blog.coldwind.top/posts/how-to-report-progress/</link><pubDate>Thu, 09 May 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/how-to-report-progress/</guid><description>&lt;img src="https://s2.loli.net/2024/05/09/RJYeMSKs5q6UdQn.jpg" alt="Featured image of post 如何在异步任务中汇报进度" />&lt;blockquote>
&lt;p>本文有对应的视频教程：&lt;a class="link" href="https://www.bilibili.com/video/BV1SD421P76s/" target="_blank" rel="noopener"
>哔哩哔哩&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>在执行异步任务时，有时候我们会希望有办法汇报进度。比如在一个 WPF 程序中，我们在界面上放了一个进度条，从而展示当前任务的进度。那么该如何汇报异步任务的进度呢？&lt;/p>
&lt;p>其实 .NET 标准库就为我们提供了实现这一功能的接口和类：&lt;code>IProgress&amp;lt;T&amp;gt;&lt;/code> 与 &lt;code>Progress&amp;lt;T&amp;gt;&lt;/code>，其中 &lt;code>T&lt;/code> 是一个泛型类型，表示要汇报的内容。如果我们希望汇报一个百分比进度，那么使用 &lt;code>double&lt;/code> 类型即可；类似地，如果我们希望汇报一些更加复杂的内容，还可以使用 &lt;code>string&lt;/code> 甚至一些自定义类与结构体。&lt;/p>
&lt;p>下面我们就来看看该如何使用吧。&lt;/p>
&lt;h2 id="搭建项目">
搭建项目
&lt;a href="#%e6%90%ad%e5%bb%ba%e9%a1%b9%e7%9b%ae" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>首先我们创建一个简易的 WPF 项目。因为这次的任务比较简单，所以我们就不遵循 MVVM 模式了，而是使用最传统的 WPF 事件注册的方式。&lt;/p>
&lt;p>它的 &lt;code>MainWindow&lt;/code> 形如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="err">...&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;StackPanel&lt;/span> &lt;span class="na">VerticalAlignment=&lt;/span>&lt;span class="s">&amp;#34;Center&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Button&lt;/span> &lt;span class="na">Width=&lt;/span>&lt;span class="s">&amp;#34;100&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">Margin=&lt;/span>&lt;span class="s">&amp;#34;0,0,0,10&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">Content=&lt;/span>&lt;span class="s">&amp;#34;Run&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">Click=&lt;/span>&lt;span class="s">&amp;#34;Button_Click&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ProgressBar&lt;/span> &lt;span class="na">Height=&lt;/span>&lt;span class="s">&amp;#34;20&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">d:Value=&lt;/span>&lt;span class="s">&amp;#34;10&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">Name=&lt;/span>&lt;span class="s">&amp;#34;progressBar&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/StackPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后在 &lt;code>MainWindow.xaml.cs&lt;/code> 中实现一些简单的逻辑：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">partial&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MainWindow&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">Window&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">MainWindow&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">InitializeComponent&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kd">async&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Button_Click&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="n">sender&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">RoutedEventArgs&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">DoJobAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CancellationToken&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">None&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">async&lt;/span> &lt;span class="n">Task&lt;/span> &lt;span class="n">DoJobAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CancellationToken&lt;/span> &lt;span class="n">token&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">token&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IsCancellationRequested&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="m">100&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">Task&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Delay&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">50&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">token&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">progressBar&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Value&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">token&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IsCancellationRequested&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我们将按钮注册的 &lt;code>Button_Click&lt;/code> 方法修改为 &lt;code>async void&lt;/code>，这样我们就可以在里面等待一个异步任务了。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">虽然 &lt;code>async void&lt;/code> 是一种非常危险的方式，但因为 &lt;code>Button&lt;/code> 控件的 &lt;code>Click&lt;/code> 事件对应委托对于函数传参及返回值的限制，这里我们不得不这样做。&lt;/div>
&lt;/div>
&lt;p>然后，我们在 &lt;code>DoJobAsync&lt;/code> 中实现后台的异步任务。这里我们简单地使用一个 &lt;code>for&lt;/code> 循环，并在其中使用 &lt;code>Task.Delay&lt;/code>，从而实现一个拥有进度的异步任务。然后，我们在每次循环中直接修改 &lt;code>progressBar&lt;/code> 控件的值。运行程序，就可以直接看到效果了：&lt;/p>
&lt;p>&lt;img src="https://s2.loli.net/2024/05/09/F6o97PzSaO4kiDc.gif"
loading="lazy"
alt="动画"
>&lt;/p>
&lt;p>这个问题难道就这么轻松地就解决了吗？其实不是的，因为在异步任务中，很可能会出现在别的线程中操作 UI 线程的资源（也就是控件及其属性），这种情况下程序会报错。所以如果使用这样的方式，通常我们还需要使用老套的 &lt;code>Dispatcher.Invoke&lt;/code> 的方式来规避这个问题。但这样就显得不够优雅了。&lt;/p>
&lt;p>那么同样的功能，我们该如何使用 &lt;code>Progress&lt;/code> 类来实现呢？&lt;/p>
&lt;h2 id="使用-progress-类">
使用 Progress 类
&lt;a href="#%e4%bd%bf%e7%94%a8-progress-%e7%b1%bb" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>首先我们需要稍稍修改一下 &lt;code>DoJobAsync&lt;/code> 方法：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">async&lt;/span> &lt;span class="n">Task&lt;/span> &lt;span class="n">DoJobAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">IProgress&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">double&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">reporter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CancellationToken&lt;/span> &lt;span class="n">token&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="m">100&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">token&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IsCancellationRequested&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">Task&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Delay&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">50&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">token&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">ConfigureAwait&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">reporter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Report&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span> &lt;span class="p">+&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">token&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IsCancellationRequested&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后，这个 &lt;code>Progress&lt;/code> 类的实例来自哪儿呢？我们再修改一下 &lt;code>Button_Click&lt;/code> 方法：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">private&lt;/span> &lt;span class="kd">async&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Button_Click&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="n">sender&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">RoutedEventArgs&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">reporter&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Progress&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">double&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="k">value&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">progressBar&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Value&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">DoJobAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">reporter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CancellationToken&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">None&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>就这样，我们只需要在使用的时候实例化一个新的即可。它除了我们前面提到的泛型，还传入了一个回调函数，表示每次 &lt;code>Report&lt;/code> 时需要执行的逻辑。这里的逻辑非常简单，只需要将传入的 &lt;code>double&lt;/code> 类型的数字赋值给进度条的 &lt;code>Value&lt;/code> 属性即可。&lt;/p>
&lt;p>那么问题来了：它是如何规避了前面提到的线程问题的呢？我们观察 &lt;code>Progress&lt;/code> 类的&lt;a class="link" href="https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Progress.cs,d23df0450d3fd0d6" target="_blank" rel="noopener"
>源代码&lt;/a>，可以发现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="n">Progress&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Capture the current synchronization context.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// If there is no current context, we use a default instance targeting the ThreadPool.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_synchronizationContext&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">SynchronizationContext&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Current&lt;/span> &lt;span class="p">??&lt;/span> &lt;span class="n">ProgressStatics&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">DefaultContext&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Debug&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Assert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">_synchronizationContext&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_invokeHandlers&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">SendOrPostCallback&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">InvokeHandlers&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在它的构造函数中，拥有一个 &lt;code>SynchronizationContext&lt;/code> 对象，它持有了当前的同步上下文。当我们在 &lt;code>Button_Click&lt;/code> 方法中声明它时，因为还在 UI 线程，所以它就保存了这个上下文。然后在它的 &lt;code>Report&lt;/code> 方法被调用时，就会在正确的同步上下文（也就是 UI 线程）中执行相关逻辑了。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">除了给构造函数传回调，&lt;code>Progress&lt;/code> 类还为我们提供了一个 &lt;code>ProgressChanged&lt;/code> 事件。注册这个事件可以实现相同的效果，并且也是在相同的同步上下文执行的。&lt;/div>
&lt;/div>
&lt;h2 id="实现自定义-progress-类">
实现自定义 Progress 类
&lt;a href="#%e5%ae%9e%e7%8e%b0%e8%87%aa%e5%ae%9a%e4%b9%89-progress-%e7%b1%bb" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>如果我们还有其他额外的需求，那么我们还可以自己实现接口，或者继承 &lt;code>Progress&lt;/code> 类。官方特意没有将这个类设为 &lt;code>sealed&lt;/code>，并且将 &lt;code>OnReport&lt;/code> 方法设为 &lt;code>virtual&lt;/code>，就是为了满足我们的这些需求。&lt;/p>
&lt;div class="notice note">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-sticky-note" aria-hidden="true">&lt;/i>Note
&lt;/div>
&lt;div class="notice-content">但是如果我们去继承这个 &lt;code>Progress&lt;/code> 类，会发现其实我们能自由发挥的空间并不大，因为它其中的很多字段（尤其是同步上下文）都是 &lt;code>private&lt;/code> 的，所以我们能做的事情基本上也只有重写 &lt;code>OnReport&lt;/code> 方法了。&lt;/div>
&lt;/div>
&lt;p>比如这里我写了一个子类，从而可以在进度完成后执行一个回调方法。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyProgress&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">Progress&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="k">where&lt;/span> &lt;span class="n">T&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">notnull&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">Action&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">_complete&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">T&lt;/span> &lt;span class="n">_maximum&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">_isCompleted&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">MyProgress&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Action&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">handler&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Action&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">complete&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">T&lt;/span> &lt;span class="n">maximum&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">:&lt;/span> &lt;span class="k">base&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">handler&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_complete&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">complete&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_maximum&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">maximum&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ProgressChanged&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">CheckCompletion&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">protected&lt;/span> &lt;span class="kd">override&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">OnReport&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">T&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">_isCompleted&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">base&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnReport&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">CheckCompletion&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object?&lt;/span> &lt;span class="n">sender&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">T&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Equals&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">_maximum&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="n">_isCompleted&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_isCompleted&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_complete&lt;/span>&lt;span class="p">?.&lt;/span>&lt;span class="n">Invoke&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后我们就可以这样使用了：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">private&lt;/span> &lt;span class="kd">async&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Button_Click&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="n">sender&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">RoutedEventArgs&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">reporter&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MyProgress&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">double&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">value&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">progressBar&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Value&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">progressBar&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Visibility&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Visibility&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Hidden&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">100&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">DoJobAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">reporter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CancellationToken&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">None&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里实现的效果是，当异步任务完成后，将会隐藏进度条。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>不知道大家看完这篇文章的感受如何。其实我在最开始了解文中提到的 &lt;code>IProgress&lt;/code> 接口以及 &lt;code>Progress&lt;/code> 类时，最大的感受是：微软究竟为我们提前准备好了多少接口和类啊🤣！&lt;/p>
&lt;p>.NET 类中有太多这样的标准库了，但我们也没有什么办法去系统地挖掘与总结。所以只能仰仗大家今后持续不断的交流与学习了。&lt;/p>
&lt;h2 id="参考">
参考
&lt;a href="#%e5%8f%82%e8%80%83" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>&lt;a class="link" href="https://www.youtube.com/watch?v=zQMNFEz5IVU" target="_blank" rel="noopener"
>How to Report Progress with Async/Await in .NET Core 3 - YouTube&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="https://www.youtube.com/watch?v=ZTKGRJy5P2M" target="_blank" rel="noopener"
>C# Advanced Async - Getting progress reports, cancelling tasks, and more - YouTube&lt;/a>&lt;/p></description></item><item><title>如何在 WPF 中实现符合 MVVM 模式的文件拖入功能</title><link>https://blog.coldwind.top/posts/drop-file-mvvm/</link><pubDate>Wed, 08 May 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/drop-file-mvvm/</guid><description>&lt;img src="https://s2.loli.net/2024/05/08/tw73xXjhTbN8pZQ.jpg" alt="Featured image of post 如何在 WPF 中实现符合 MVVM 模式的文件拖入功能" />&lt;p>本篇文章对应的教学视频链接：&lt;a class="link" href="https://www.bilibili.com/video/BV1NF4m1A7SD/" target="_blank" rel="noopener"
>WPF中如何实现符合MVVM模式的文件拖入功能&lt;/a>&lt;/p>
&lt;h2 id="原始方式">
原始方式
&lt;a href="#%e5%8e%9f%e5%a7%8b%e6%96%b9%e5%bc%8f" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在 WPF 中，实现文件拖入功能并不难。稍微在网上搜索一下，就能够得到答案。比如现在有一个窗口，我们只需要设置它的 &lt;code>AllowDrop&lt;/code> 属性为 &lt;code>True&lt;/code>，然后在 &lt;code>Drop&lt;/code> 事件中处理即可。形如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="err">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">AllowDrop=&lt;/span>&lt;span class="s">&amp;#34;True&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">Drop=&lt;/span>&lt;span class="s">&amp;#34;Window_Drop&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">partial&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MainWindow&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">Window&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">MainWindow&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">InitializeComponent&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Window_Drop&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="n">sender&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">DragEventArgs&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetDataPresent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DataFormats&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FileDrop&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">files&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">[])&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetData&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DataFormats&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FileDrop&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 处理拖入的文件&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="添加视图模型">
添加视图模型
&lt;a href="#%e6%b7%bb%e5%8a%a0%e8%a7%86%e5%9b%be%e6%a8%a1%e5%9e%8b" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>但问题是，如果现在 &lt;code>Window&lt;/code> 拥有一个视图模型（ViewModel），形如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MainViewModel&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">ViewModelBase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kt">string?&lt;/span> &lt;span class="n">_fileName&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string?&lt;/span> &lt;span class="n">FileName&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">get&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">_fileName&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">set&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_fileName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">OnPropertyChanged&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">nameof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">FileName&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">这里我们假定已经实现了 &lt;code>ViewModelBase&lt;/code> 类，它实现了 &lt;code>INotifyPropertyChanged&lt;/code> 接口，并提供了 &lt;code>OnPropertyChanged&lt;/code> 方法以便于通知属性发生了变化。&lt;/div>
&lt;/div>
&lt;p>然后 &lt;code>Window&lt;/code> 上面有一个 &lt;code>TextBox&lt;/code> 绑定了这个属性，这又该怎么办呢？&lt;/p>
&lt;p>这里有两种比较简单粗暴的方式：&lt;/p>
&lt;ol>
&lt;li>为 &lt;code>TextBox&lt;/code> 添加一个 &lt;code>Name&lt;/code>，然后在 &lt;code>Window&lt;/code> 的 &lt;code>Drop&lt;/code> 事件中直接修改 &lt;code>TextBox&lt;/code> 的 &lt;code>Text&lt;/code> 属性，进而使用依赖属性的一些方法来通知绑定的 ViewModel 属性发生了变化&lt;/li>
&lt;li>在 &lt;code>Window&lt;/code> 的 &lt;code>Drop&lt;/code> 事件中直接修改 &lt;code>ViewModel&lt;/code> 的属性（获取 &lt;code>Window.DataContext&lt;/code>，并将其转为 &lt;code>MainViewModel&lt;/code> 类型），然后在 &lt;code>ViewModel&lt;/code> 中实现 &lt;code>INotifyPropertyChanged&lt;/code> 接口，进而通知 &lt;code>TextBox&lt;/code> 的 &lt;code>Text&lt;/code> 属性发生了变化&lt;/li>
&lt;/ol>
&lt;p>这两种方式都很直接，而且其实都不违背 MVVM 模式。但是这两种方式并不优雅，所以这里我们借助行为（Behaviors）来实现一个更加优雅且通用的方式。&lt;/p>
&lt;h2 id="使用行为">
使用行为
&lt;a href="#%e4%bd%bf%e7%94%a8%e8%a1%8c%e4%b8%ba" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>首先，我们需要安装 &lt;code>Microsoft.Xaml.Behaviors.Wpf&lt;/code> 包。然后我们可以创建一个 &lt;code>DropFileBehavior&lt;/code> 类，形如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">DropFileBehavior&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">Behavior&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">FrameworkElement&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">[]?&lt;/span> &lt;span class="n">Data&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">get&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">[]?)&lt;/span>&lt;span class="n">GetValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">FilesProperty&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">set&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">SetValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">FilesProperty&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">DependencyProperty&lt;/span> &lt;span class="n">FilesProperty&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">DependencyProperty&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Register&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">nameof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Data&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">typeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">[]),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">typeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DropFileBehavior&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="n">UIPropertyMetadata&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">protected&lt;/span> &lt;span class="kd">override&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">OnAttached&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">AssociatedObject&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AllowDrop&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">AssociatedObject&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Drop&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="n">DropHandler&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">protected&lt;/span> &lt;span class="kd">override&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">OnDetaching&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">AssociatedObject&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Drop&lt;/span> &lt;span class="p">-=&lt;/span> &lt;span class="n">DropHandler&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">DropHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="n">sender&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">DragEventArgs&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetDataPresent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DataFormats&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FileDrop&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Data&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">[])&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetData&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DataFormats&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FileDrop&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这个行为大致实现的功能是：&lt;/p>
&lt;ol>
&lt;li>当附加到一个 &lt;code>FrameworkElement&lt;/code> 上时，将其 &lt;code>AllowDrop&lt;/code> 属性设置为 &lt;code>True&lt;/code>，并注册 &lt;code>Drop&lt;/code> 事件&lt;/li>
&lt;li>当拖入文件时，将文件路径保存到 &lt;code>Data&lt;/code> 依赖属性中&lt;/li>
&lt;/ol>
&lt;p>然后我们就可以在 XAML 中使用这个行为了（因为这里我们声明的 &lt;code>Data&lt;/code> 属性是一个数组，所以我们稍微修改 &lt;code>MainViewModel&lt;/code> 中相关属性的名称及类型，从而实现绑定功能）：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="err">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:i=&lt;/span>&lt;span class="s">&amp;#34;http://schemas.microsoft.com/xaml/behaviors&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:local=&lt;/span>&lt;span class="s">&amp;#34;clr-namespace:YourNamespace&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;i:Interaction.Behaviors&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;local:DropFileBehavior&lt;/span> &lt;span class="na">Data=&lt;/span>&lt;span class="s">&amp;#34;{Binding FileNames, Mode=OneWayToSource}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/i:Interaction.Behaviors&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBox&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;{Binding FileNames[0]}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>注意这里，我们在书写行为的 &lt;code>Data&lt;/code> 属性的绑定时，使用了 &lt;code>Mode=OneWayToSource&lt;/code>，这是因为我们只需要将数据从视图传递到视图模型，而不需要反向传递。并且如果不写 &lt;code>Mode&lt;/code>，它默认将会是 &lt;code>OneWay&lt;/code>，导致可能无法正确通知到 &lt;code>ViewModel&lt;/code>。&lt;/p>
&lt;h2 id="制作界面">
制作界面
&lt;a href="#%e5%88%b6%e4%bd%9c%e7%95%8c%e9%9d%a2" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>最后，我们还可以搞一个“酷炫”的界面，形如：&lt;/p>
&lt;img src="https://s2.loli.net/2024/05/08/ZNiGDOtz1AJTnXW.gif" style="width:400px" />
&lt;p>首先，我们可以在窗口中添加这样一个置于上方的控件：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Grid&lt;/span> &lt;span class="na">Name=&lt;/span>&lt;span class="s">&amp;#34;dropFilePanel&amp;#34;&lt;/span> &lt;span class="na">Visibility=&lt;/span>&lt;span class="s">&amp;#34;Hidden&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Border&lt;/span> &lt;span class="na">Background=&lt;/span>&lt;span class="s">&amp;#34;White&amp;#34;&lt;/span> &lt;span class="na">Opacity=&lt;/span>&lt;span class="s">&amp;#34;0.8&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="na">HorizontalAlignment=&lt;/span>&lt;span class="s">&amp;#34;Center&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">VerticalAlignment=&lt;/span>&lt;span class="s">&amp;#34;Center&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;将文件拖放到此处&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Rectangle&lt;/span> &lt;span class="na">Width=&lt;/span>&lt;span class="s">&amp;#34;200&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">Height=&lt;/span>&lt;span class="s">&amp;#34;100&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">Stroke=&lt;/span>&lt;span class="s">&amp;#34;Gray&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">RadiusX=&lt;/span>&lt;span class="s">&amp;#34;10&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">RadiusY=&lt;/span>&lt;span class="s">&amp;#34;10&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">StrokeDashArray=&lt;/span>&lt;span class="s">&amp;#34;3,4&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">StrokeThickness=&lt;/span>&lt;span class="s">&amp;#34;2&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>同时，因为我们现在有了这个专门的用于放置文件的面板，所以我们可以将之前添加给窗口的行为转移到它身上，形如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Grid&lt;/span> &lt;span class="na">Name=&lt;/span>&lt;span class="s">&amp;#34;dropFilePanel&amp;#34;&lt;/span> &lt;span class="na">Visibility=&lt;/span>&lt;span class="s">&amp;#34;Hidden&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;i:Interaction.Behaviors&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;local:DropFileBehavior&lt;/span> &lt;span class="na">Data=&lt;/span>&lt;span class="s">&amp;#34;{Binding FileNames, Mode=OneWayToSource}&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/i:Interaction.Behaviors&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但是我们要控制它在合适的时机出现与消失。这里我们可以使用触发器与行为来快速地实现这一效果。具体来说，我们可以给窗口添加这样的触发器：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="err">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:b=&lt;/span>&lt;span class="s">&amp;#34;http://schemas.microsoft.com/xaml/behaviors&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;b:Interaction.Triggers&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;b:EventTrigger&lt;/span> &lt;span class="na">EventName=&lt;/span>&lt;span class="s">&amp;#34;DragEnter&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;b:ChangePropertyAction&lt;/span> &lt;span class="na">TargetObject=&lt;/span>&lt;span class="s">&amp;#34;{Binding ElementName=dropFilePanel}&amp;#34;&lt;/span> &lt;span class="na">PropertyName=&lt;/span>&lt;span class="s">&amp;#34;Visibility&amp;#34;&lt;/span> &lt;span class="na">Value=&lt;/span>&lt;span class="s">&amp;#34;Visible&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/b:EventTrigger&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;b:EventTrigger&lt;/span> &lt;span class="na">EventName=&lt;/span>&lt;span class="s">&amp;#34;DragLeave&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;b:ChangePropertyAction&lt;/span> &lt;span class="na">TargetObject=&lt;/span>&lt;span class="s">&amp;#34;{Binding ElementName=dropFilePanel}&amp;#34;&lt;/span> &lt;span class="na">PropertyName=&lt;/span>&lt;span class="s">&amp;#34;Visibility&amp;#34;&lt;/span> &lt;span class="na">Value=&lt;/span>&lt;span class="s">&amp;#34;Hidden&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/b:EventTrigger&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/b:Interaction.Triggers&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样，当鼠标拖入窗口时，&lt;code>dropFilePanel&lt;/code> 就会显示出来；当鼠标拖出窗口时，&lt;code>dropFilePanel&lt;/code> 就会被隐藏。&lt;/p>
&lt;p>实际测试后会发现，当我们将文件拖到上方并松开左键后，虽然行为得到了正确的响应，但面板并没有消失。这是因为我们上面写的触发器只会在鼠标拖动状态离开窗口后才会隐藏面板。所以这里，我们可以再为面板添加一个触发器，并在它触发了 &lt;code>Drop&lt;/code> 事件后将自身隐藏：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Grid&lt;/span> &lt;span class="na">Name=&lt;/span>&lt;span class="s">&amp;#34;dropFilePanel&amp;#34;&lt;/span> &lt;span class="na">Visibility=&lt;/span>&lt;span class="s">&amp;#34;Hidden&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;b:Interaction.Triggers&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;b:EventTrigger&lt;/span> &lt;span class="na">EventName=&lt;/span>&lt;span class="s">&amp;#34;Drop&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;b:ChangePropertyAction&lt;/span> &lt;span class="na">PropertyName=&lt;/span>&lt;span class="s">&amp;#34;Visibility&amp;#34;&lt;/span> &lt;span class="na">Value=&lt;/span>&lt;span class="s">&amp;#34;Hidden&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/b:EventTrigger&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/b:Interaction.Triggers&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样，上面动图中的效果就实现了。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>WPF 开发必然会经常和控件的事件打交道。但很多时候，如果我们希望遵循 MVVM 模式，可能就会不知所措。相信大家通过这篇文章的代码，都能够充分领略到使用触发器与行为的强大之处。当然，这里只是一个简单的例子，实际开发中，我们还可以为上面的例子添加更多丰富的功能及特效。这些就有待大家的探索了。&lt;/p>
&lt;p>大家如果有什么自己的好方法，也欢迎在文章评论区留言，分享给大家。&lt;/p></description></item><item><title>如何使用 appsettings.json 配置文件？</title><link>https://blog.coldwind.top/posts/how-to-use-appsettings/</link><pubDate>Mon, 22 Apr 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/how-to-use-appsettings/</guid><description>&lt;img src="https://s2.loli.net/2024/04/22/7ZhNX9B6CefQbuE.png" alt="Featured image of post 如何使用 appsettings.json 配置文件？" />&lt;p>在 .NET Core 项目中，我们可以使用 &lt;code>appsettings.json&lt;/code> 配置文件来存储应用程序的配置信息。在这篇文章中，我们将学习如何使用 &lt;code>appsettings.json&lt;/code> 配置文件。&lt;/p>
&lt;p>&lt;code>appsettings.json&lt;/code> 是一个相较于 &lt;code>App.config&lt;/code> 更加灵活的配置文件，是 .NET Core 以来新增的一种配置方式，提供了更多的灵活性。&lt;/p>
&lt;h2 id="快速入门">
快速入门
&lt;a href="#%e5%bf%ab%e9%80%9f%e5%85%a5%e9%97%a8" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>我们可以在项目中创建一个 &lt;code>appsettings.json&lt;/code> 文件，然并将其生成操作设置为「较新时复制」或「总是复制」，这样在项目构建时，&lt;code>appsettings.json&lt;/code> 文件会被复制到输出目录中。&lt;/p>
&lt;p>然后我们可以在其中添加如下内容：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;AppSettings&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;LogLevel&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="s2">&amp;#34;Warning&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;ConnectionStrings&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;Default&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;this is the connection string&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样我们就可以尝试读取了。我们使用 NuGet 包管理器安装 &lt;code>Microsoft.Extensions.Configuration.Json&lt;/code> 包。它会隐式安装 &lt;code>Microsoft.Extensions.Configuration&lt;/code> 等依赖项，这些我们不需要显式安装。&lt;/p>
&lt;p>然后我们可以在代码中读取配置文件：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">Microsoft.Extensions.Configuration&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">configuration&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ConfigurationBuilder&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">AddJsonFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;appsettings.json&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">optional&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">reloadOnChange&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Build&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样我们就可以获取上面的配置信息了：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">logLevel&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">configuration&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s">&amp;#34;AppSettings:LogLevel&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">connectionString&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">configuration&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s">&amp;#34;AppSettings:ConnectionStrings:Default&amp;#34;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里的形如 &lt;code>AppSettings.LogLevel&lt;/code> 是一种特殊的写法，简单来说就是借助 &lt;code>:&lt;/code> 来表示 JSON 中的层级关系。&lt;/p>
&lt;p>如果要获取的配置项是一个数字，我们除了可以先通过上述方式获取到字符串，进而使用 &lt;code>int.Parse&lt;/code> 或 &lt;code>Convert.ToInt32&lt;/code> 等方法进行转换，还可以使用 &lt;code>GetValue&lt;/code> 方法：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 传统方法&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">logLevel&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">configuration&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s">&amp;#34;AppSettings:LogLevel&amp;#34;&lt;/span>&lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 使用 GetValue 方法&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">logLevel&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">configuration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetValue&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="s">&amp;#34;AppSettings:LogLevel&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>对于连接字符串，我们还可以使用 &lt;code>GetConnectionString&lt;/code> 方法：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">connectionString&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">configuration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetConnectionString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Default&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="可选与自动重载">
可选与自动重载
&lt;a href="#%e5%8f%af%e9%80%89%e4%b8%8e%e8%87%aa%e5%8a%a8%e9%87%8d%e8%bd%bd" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在上面的代码中，我们可以看到 &lt;code>AddJsonFile&lt;/code> 方法有两个参数，&lt;code>optional&lt;/code> 和 &lt;code>reloadOnChange&lt;/code>：&lt;/p>
&lt;ul>
&lt;li>&lt;code>optional&lt;/code> 参数表示是否允许配置文件不存在，如果设置为 &lt;code>false&lt;/code>，则会抛出异常，否则会忽略。&lt;/li>
&lt;li>&lt;code>reloadOnChange&lt;/code> 参数表示是否在配置文件发生变化时重新加载配置文件。如果设置为 &lt;code>true&lt;/code>，则会在配置文件发生变化时重新加载配置文件。&lt;/li>
&lt;/ul>
&lt;p>比如我们可以用下面的例子测试自动重载的效果：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">configuration&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ConfigurationBuilder&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">AddJsonFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;appsettings.json&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">optional&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">reloadOnChange&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Build&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">configuration&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s">&amp;#34;AppSettings:LogLevel&amp;#34;&lt;/span>&lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Thread&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1000&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在运行程序后，我们可以修改 &lt;code>appsettings.json&lt;/code> 文件中的 &lt;code>LogLevel&lt;/code> 配置，然后我们会发现程序会自动重新加载配置文件。注意这里我们修改的是输出目录（也就是 &lt;code>.exe&lt;/code> 文件所在位置）下的 &lt;code>appsettings.json&lt;/code> 文件，而不是项目中的 &lt;code>appsettings.json&lt;/code> 文件。&lt;/p>
&lt;h2 id="添加多个-json-文件">
添加多个 JSON 文件
&lt;a href="#%e6%b7%bb%e5%8a%a0%e5%a4%9a%e4%b8%aa-json-%e6%96%87%e4%bb%b6" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>如果只能添加一个 JSON 文件，那么配置文件的灵活性就大大降低了。事实上，我们可以通过多次调用 &lt;code>AddJsonFile&lt;/code> 方法来添加多个 JSON 文件。一个典型的情形是添加一个 &lt;code>appsettings.Development.json&lt;/code> 文件，用于存储开发环境的配置信息。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">configuration&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ConfigurationBuilder&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">AddJsonFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;appsettings.json&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">optional&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">reloadOnChange&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">AddJsonFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;appsettings.Development.json&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">optional&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">reloadOnChange&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Build&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样我们就可以在 &lt;code>appsettings.Development.json&lt;/code> 文件中存储开发环境的配置信息，而在 &lt;code>appsettings.json&lt;/code> 文件中存储通用的配置信息。&lt;/p>
&lt;p>不仅如此，这二者之间存在优先级，或者说覆盖关系。具体来说：&lt;/p>
&lt;ul>
&lt;li>如果 &lt;code>appsettings.json&lt;/code> 和 &lt;code>appsettings.Development.json&lt;/code> 中都有相同的配置项，那么 &lt;code>appsettings.Development.json&lt;/code> 中的配置项会覆盖 &lt;code>appsettings.json&lt;/code> 中的配置项&lt;/li>
&lt;li>如果 &lt;code>appsettings.Development.json&lt;/code> 中没有某个配置项，而 &lt;code>appsettings.json&lt;/code> 中有，那么会使用 &lt;code>appsettings.json&lt;/code> 中的配置项&lt;/li>
&lt;li>如果 &lt;code>appsettings.Development.json&lt;/code> 中有某个配置项，而 &lt;code>appsettings.json&lt;/code> 中没有，那么会使用 &lt;code>appsettings.Development.json&lt;/code> 中的配置项&lt;/li>
&lt;/ul>
&lt;h2 id="使用强类型配置">
使用强类型配置
&lt;a href="#%e4%bd%bf%e7%94%a8%e5%bc%ba%e7%b1%bb%e5%9e%8b%e9%85%8d%e7%bd%ae" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在上面的例子中，我们使用 &lt;code>configuration[&amp;quot;AppSettings:LogLevel&amp;quot;]&lt;/code> 来获取配置信息，这种方式是一种弱类型的方式。我们也可以使用强类型的方式来获取配置信息。&lt;/p>
&lt;p>我们修改一下 &lt;code>appsettings.json&lt;/code> 文件中的配置项：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;UserSettings&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;Name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Alice&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;Age&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">18&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;IsActive&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后我们定义一个强类型的配置类：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">UserSettings&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Age&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">IsActive&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在获取配置前，我们还需要安装一个 NuGet 包：&lt;code>Microsoft.Extensions.Options.ConfigurationExtensions&lt;/code>。然后我们就可以这样获取配置信息：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">userSettings&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">configuration&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetSection&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;UserSettings&amp;#34;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">Get&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">UserSettings&lt;/span>&lt;span class="p">&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样我们就可以获取到 &lt;code>UserSettings&lt;/code> 对象了，然后就可以使用 &lt;code>userSettings.Name&lt;/code>、&lt;code>userSettings.Age&lt;/code>、&lt;code>userSettings.IsActive&lt;/code> 来获取配置信息了。&lt;/p>
&lt;p>但是需要注意，因为这里的 &lt;code>userSettings&lt;/code> 实例已经初始化，所以前面提到的自动重载功能不再生效。如果需要自动重载，我们需要重新获取 &lt;code>userSettings&lt;/code> 对象。&lt;/p>
&lt;h2 id="添加环境变量和命令行参数">
添加环境变量和命令行参数
&lt;a href="#%e6%b7%bb%e5%8a%a0%e7%8e%af%e5%a2%83%e5%8f%98%e9%87%8f%e5%92%8c%e5%91%bd%e4%bb%a4%e8%a1%8c%e5%8f%82%e6%95%b0" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在 .NET Core 中，我们还可以通过环境变量和命令行参数来覆盖配置文件中的配置信息。我们需要再安装两个 NuGet 包：&lt;/p>
&lt;ul>
&lt;li>&lt;code>Microsoft.Extensions.Configuration.EnvironmentVariables&lt;/code>&lt;/li>
&lt;li>&lt;code>Microsoft.Extensions.Configuration.CommandLine&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>然后我们可以这样添加环境变量和命令行参数：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">configuration&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">ConfigurationBuilder&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">AddJsonFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;appsettings.json&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">optional&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">reloadOnChange&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">AddEnvironmentVariables&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">AddCommandLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Build&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样我们就可以通过环境变量和命令行参数来覆盖配置文件中的配置信息了。&lt;/p>
&lt;p>比如我们可以创建一个 &lt;code>.bat&lt;/code> 批处理文件：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bat" data-lang="bat">&lt;span class="line">&lt;span class="cl">&lt;span class="p">@&lt;/span>&lt;span class="k">echo&lt;/span> off
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">set&lt;/span> &lt;span class="nv">UserSettings__Name&lt;/span>&lt;span class="p">=&lt;/span>Bob
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">set&lt;/span> &lt;span class="nv">UserSettings__Age&lt;/span>&lt;span class="p">=&lt;/span>20
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">.\Demo.exe
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>或者还可以使用 PowerShell：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$env:UserSettings__Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;Bob&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$env:UserSettings__Age&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mf">20&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">.\&lt;/span>&lt;span class="n">Demo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">exe&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>相信通过这篇文章，大家已经认识到了 &lt;code>appsettings.json&lt;/code> 配置文件的强大之处。它不仅提供了一种灵活的配置方式，还提供了多种配置方式的组合，使得我们可以更加灵活地配置应用程序。&lt;/p>
&lt;p>但是它也有一些局限性。最重要的一条就是它的配置项是“只读”的，也就是不能像 &lt;code>App.config&lt;/code> 那样在运行时方便地修改配置项。毕竟，一个项目中可能存在多个配置项，而不是只有一个 &lt;code>appsettings.json&lt;/code> 文件。此时如果修改了，该保存到哪个文件呢？&lt;/p>
&lt;p>当然，如果只有一个配置文件，那么 &lt;code>appsettings.json&lt;/code> 是一个不错的选择。比如我们可以使用 &lt;code>Newtonsoft.Json&lt;/code> 来轻松地写入 JSON 文件，这样就可以实现配置项的修改了。&lt;/p>
&lt;p>最后，其实通常情况下，我们并不会使用上面的方式读取配置项，而是会更进一步，使用 &lt;code>Host&lt;/code> 作为整个程序的入口，并读取配置、注入服务等。在之后的文章中，我们会学习如何使用 &lt;code>Host&lt;/code> 来构建一个 .NET 应用程序。&lt;/p></description></item><item><title>如何用 Rx.NET 来模拟情景短剧《恐惧症研讨会》</title><link>https://blog.coldwind.top/posts/phobia-workshop/</link><pubDate>Sat, 20 Apr 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/phobia-workshop/</guid><description>&lt;img src="https://s2.loli.net/2024/04/20/tAq5BvYJUkQgReP.png" alt="Featured image of post 如何用 Rx.NET 来模拟情景短剧《恐惧症研讨会》" />&lt;p>不知道大家有没有看过这样一个视频：&lt;/p>
&lt;div class="video-wrapper">
&lt;iframe src="https://player.bilibili.com/player.html?as_wide=1&amp;amp;high_quality=1&amp;amp;page=1&amp;bvid=BV1js411z7wf&amp;mute=0&amp;autoplay=0"
scrolling="no"
frameborder="no"
framespacing="0"
allowfullscreen="true"
>
&lt;/iframe>
&lt;/div>
&lt;p>（或者也可以看油管上的 &lt;a class="link" href="https://www.youtube.com/watch?v=koNwUeG-iKE" target="_blank" rel="noopener"
>原版视频&lt;/a>）&lt;/p>
&lt;p>我们这次就来玩一玩，如何使用 Rx.NET 来模拟这个情景短剧。&lt;/p>
&lt;h2 id="简单分析每个人的特点">
简单分析每个人的特点
&lt;a href="#%e7%ae%80%e5%8d%95%e5%88%86%e6%9e%90%e6%af%8f%e4%b8%aa%e4%ba%ba%e7%9a%84%e7%89%b9%e7%82%b9" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>通过观看视频，我们发现一共有五个人，且这五个人各有特点，或者说各自会在特定情况下触发自己的恐惧症，进而发出尖叫。具体来说：&lt;/p>
&lt;ul>
&lt;li>Lee：对于“AAGH!”（也就是“啊！”）这个词很恐惧
&lt;ul>
&lt;li>且这个词必须是别人发出的&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Jim：对于道歉（或者说“Sorry”这个词）很恐惧
&lt;ul>
&lt;li>自己说的这个词也是可以触发自己的恐惧的&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Karen：对于重复的话很恐惧
&lt;ul>
&lt;li>两句重复的话必须都是别人说的&lt;/li>
&lt;li>（从视频中来看，两句重复的话甚至可以间隔很久，但这种情况难以概括，且视频中其他时候也有重复的话，但并未触发，所以存在 BUG，暂不考虑）&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Ronnie：对于“尴尬的沉默”很恐惧
&lt;ul>
&lt;li>也就是说，如果有人说了一句话，然后没有人回应，那么就会触发&lt;/li>
&lt;li>前提是必须有人先说了什么，而不是打一开始就没有任何人说话&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Tim：对于别人因恐惧而发出尖叫这件事情感到恐惧，并且会吓出狗叫
&lt;ul>
&lt;li>当其他有人发出了恐惧的尖叫，且之后不再会有人尖叫时，他会发出狗叫&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>大家可以多看几遍视频，尤其是靠近后面的地方，他们连续相继发出尖叫声的片段，看看我上面总结的是否正确。&lt;/p>
&lt;p>那么现在，我们就来模拟这个情景短剧吧。&lt;/p>
&lt;h2 id="实现消息总线">
实现消息总线
&lt;a href="#%e5%ae%9e%e7%8e%b0%e6%b6%88%e6%81%af%e6%80%bb%e7%ba%bf" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在模拟每个人之前，我们首先需要有一个消息总线（Message Bus）。有了这个总线，我们才可以既让所有人都能够收听（或者说订阅）这个总线，又可以向总线中发送消息。&lt;/p>
&lt;p>在 Rx.NET 中，&lt;code>Subject&lt;/code> 这个类型就是典型的能够实现这一效果的类。我们可以使用它来实现一个消息总线。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MessageBus&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">IDisposable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 内部使用一个 Subject 对象&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">Subject&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Message&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_subject&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 当用于订阅时，返回一个 IObservable&amp;lt;Message&amp;gt; 对象，从而封装类中其他功能&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">IObservable&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Message&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">Messages&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">_subject&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AsObservable&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 当向总线中发送消息时，底层会调用 Subject 的 OnNext 方法&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">SendMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Message&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Content&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="s">&amp;#34;exit&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_subject&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnCompleted&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_subject&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">OnNext&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Dispose&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_subject&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Dispose&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>同时，我们也需要一个 &lt;code>Message&lt;/code> 类型，从而更好地让接下来的每一个人都能够判断自己是否应该发出尖叫。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">record&lt;/span> &lt;span class="nc">Message&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="n">Sender&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Content&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>是的，一个简单的记录类就可以满足我们的需求了。上面的每一个人，它们都只需要知道是谁说的，以及说了什么，就足够处理各自的逻辑了。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">在 &lt;a class="link" href="https://github.com/reactiveui/ReactiveUI" target="_blank" rel="noopener"
>ReactiveUI&lt;/a> 中也有一个消息总线类型，名叫 &lt;code>MessageBus&lt;/code>。它底层其实就是借助了一个 &lt;code>Subject&lt;/code> 来实现的。当然实际上更复杂一些，因为还有与 &lt;code>Scheduler&lt;/code> 相关的一些额外的功能，所以它额外实现了一个名为 &lt;a class="link" href="https://github.com/reactiveui/ReactiveUI/blob/main/src/ReactiveUI/Scheduler/ScheduledSubject.cs" target="_blank" rel="noopener"
>&lt;code>ScheduledSubject&lt;/code>&lt;/a> 的类。&lt;/div>
&lt;/div>
&lt;h2 id="模拟每一个人的行为">
模拟每一个人的行为
&lt;a href="#%e6%a8%a1%e6%8b%9f%e6%af%8f%e4%b8%80%e4%b8%aa%e4%ba%ba%e7%9a%84%e8%a1%8c%e4%b8%ba" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>下面我们就根据出场顺序，来逐个模拟每个人的逻辑吧。这里为了简单起见，我们统一使用小写，并且为所有人设定了一个固定的延迟。此外，还需要给两个人额外的时间：&lt;/p>
&lt;ul>
&lt;li>给 Ronnie 一个时间阈值，表示多久之后才会被她判定为长时间的“尴尬的沉默”&lt;/li>
&lt;li>给 Tim 一个相对更长一点的延迟，从而让他能够在确保其他人都不再尖叫之后，才发出自己的狗叫&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">reactionDelay&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">TimeSpan&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FromSeconds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">0.25&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">ronnieSilenceThreshold&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">TimeSpan&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FromSeconds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">3&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">timReactionDelay&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">TimeSpan&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FromSeconds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">0.3&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>同时，我们还要声明前面定义好的消息总线：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">bus&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MessageBus&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样，每个人都能够收听这个总线，并且自己发出的尖叫也要传递给这个总线。&lt;/p>
&lt;h3 id="lee">
Lee
&lt;a href="#lee" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>Lee 的逻辑很简单，只要听到了别人说的 “AAGH!”这个词，就会发出尖叫。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">agent1&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">bus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Messages&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Where&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">m&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Content&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="s">&amp;#34;aagh&amp;#34;&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Sender&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="s">&amp;#34;agent1&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 别人说的 aagh&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Delay&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">reactionDelay&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Subscribe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">_&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">bus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SendMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;agent1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;aagh&amp;#34;&lt;/span>&lt;span class="p">)));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="jim">
Jim
&lt;a href="#jim" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>Jim 的逻辑也很简单，只要听到了 “Sorry” 这个词（不用管是谁发出的），就会发出尖叫。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">agent2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">bus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Messages&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Where&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">m&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Content&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="s">&amp;#34;sorry&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 无论是谁说的 sorry&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Delay&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">reactionDelay&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Subscribe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">_&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">bus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SendMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;agent2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;aagh&amp;#34;&lt;/span>&lt;span class="p">)));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="karen">
Karen
&lt;a href="#karen" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>Karen 的逻辑稍微复杂一点，因为她需要判断两句话是否重复，且都是别人说的。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">agent3&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">bus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Messages&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Buffer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Where&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ms&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">ms&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Count&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="m">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">ms&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="n">Content&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="n">ms&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="n">Content&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">ms&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="n">Sender&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="s">&amp;#34;agent3&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">ms&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="n">Sender&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="s">&amp;#34;agent3&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Delay&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">reactionDelay&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Subscribe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">_&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">bus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SendMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;agent3&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;aagh&amp;#34;&lt;/span>&lt;span class="p">)));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="ronnie">
Ronnie
&lt;a href="#ronnie" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>Ronnie 的逻辑也比较简单，只要有人说了话，然后没有人回应，就会发出尖叫。那么 Rx 中的 &lt;code>Throttle&lt;/code> 方法简直就是为她量身打造的。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">agent4&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">bus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Messages&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Throttle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ronnieSilenceThreshold&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// .Delay(reactionDelay) // 这句也可以不写&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Subscribe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">_&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">bus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SendMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;agent4&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;aagh&amp;#34;&lt;/span>&lt;span class="p">)));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="tim">
Tim
&lt;a href="#tim" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>Tim 其实与 Ronnie 类似，只要有人发出了尖叫，然后之后没有人再发出尖叫，他就会发出狗叫。所以我们同样可以使用 &lt;code>Throttle&lt;/code> 方法来实现。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">agent5&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">bus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Messages&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Where&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">m&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Content&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="s">&amp;#34;aagh&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Throttle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">timReactionDelay&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Subscribe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">_&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">bus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SendMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;agent5&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;woof&amp;#34;&lt;/span>&lt;span class="p">)));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="放在一起">
放在一起
&lt;a href="#%e6%94%be%e5%9c%a8%e4%b8%80%e8%b5%b7" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>最后，我们将上面的代码放在一起。为了能够便于观察效果，我们使用 LINQPad 来简单地搭建这段代码，并且额外添加一个 &lt;code>agent&lt;/code>，代表用户的输入。这样，我们就可以通过输入来模拟每个人的发言了。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">bool&lt;/span> &lt;span class="n">isCompleted&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">bus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Messages&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">Subscribe&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">m&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">$&amp;#34;[{DateTime.Now: mm:ss.fff}] {m.Sender}: {m.Content}&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">isCompleted&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">while&lt;/span> &lt;span class="p">(!&lt;/span>&lt;span class="n">isCompleted&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">input&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Util&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ReadLine&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bus&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SendMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">input&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>完整版代码可以查看&lt;a class="link" href="https://gist.github.com/BYJRK/6912c2df1e6dd5b705400c006b6be627" target="_blank" rel="noopener"
>这个 Gist&lt;/a>。&lt;/p>
&lt;p>运行看一下效果。输入“aagh”会看到：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">[55:26.812] user: aagh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[55:27.112] agent1: aagh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[55:27.362] agent3: aagh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[55:27.625] agent1: aagh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[55:27.941] agent5: woof
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>输入“sorry”会看到：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">[55:34.985] user: sorry
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[55:35.236] agent2: aagh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[55:35.499] agent1: aagh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[55:35.763] agent3: aagh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[55:36.027] agent1: aagh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[55:36.339] agent5: woof
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>通过这个简单的例子，我们可以看到，Rx.NET 的强大之处。我们可以通过简单的类似 LINQ 一样的查询，就能够实现复杂的逻辑。这种方式不仅简洁，而且易于理解，同时也能够很好地处理异步的情况。试想一下，如果我们使用传统的多线程或异步编程来实现相同的效果，那么代码会变得多么复杂。&lt;/p>
&lt;p>之后我们还会继续探讨 Rx.NET 的更多用法，用更多实际且生动的例子，来帮助大家更好地理解这个库。&lt;/p></description></item><item><title>为什么 IEnumerable 对象没有 ForEach 方法？</title><link>https://blog.coldwind.top/posts/why-ienumerable-no-foreach/</link><pubDate>Wed, 17 Apr 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/why-ienumerable-no-foreach/</guid><description>&lt;img src="https://s2.loli.net/2024/04/17/diwtgBYmexonr14.jpg" alt="Featured image of post 为什么 IEnumerable 对象没有 ForEach 方法？" />&lt;h2 id="便捷的-foreach-方法">
便捷的 ForEach 方法
&lt;a href="#%e4%be%bf%e6%8d%b7%e7%9a%84-foreach-%e6%96%b9%e6%b3%95" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>C# 中，&lt;code>List&lt;/code> 类型（其他还包括 &lt;code>ImmutableList&lt;/code> 等）拥有一个名为 &lt;code>ForEach&lt;/code> 的方法。它的作用可以理解为传统 &lt;code>foreach&lt;/code> 语句的另一种函数式的写法。比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">list&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">5&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 传统的 foreach 语句&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">list&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 便捷的 ForEach 方法可以实现相同的效果&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">list&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ForEach&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>（One-liner 狂喜）&lt;/p>
&lt;p>此外，&lt;code>Array&lt;/code> 类还拥有一个 &lt;code>ForEach&lt;/code> 静态方法，同样可以实现类似的功能。但很快我们就会发现，为什么这么好用的方法，我们却不能用在一个 &lt;code>IEnumerable&lt;/code> （这里指的是泛型接口 &lt;code>IEnumerable&amp;lt;T&amp;gt;&lt;/code>，下面不再赘述）接口类型上呢？&lt;/p>
&lt;div class="notice note">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-sticky-note" aria-hidden="true">&lt;/i>Note
&lt;/div>
&lt;div class="notice-content">在某些读者可能会发问之前，我把“丑话说在前面”：将一个 &lt;code>IEnumerable&lt;/code> 类型使用 &lt;code>ToList()&lt;/code> 方法转为 &lt;code>List&lt;/code>，只为使用 &lt;code>ForEach()&lt;/code> 绝对不是一个好主意，因为这很可能会涉及到消耗 LINQ 语句，创建新对象，以及逐个填充元素等。&lt;/div>
&lt;/div>
&lt;p>其实这样设计是有原因的。我大概总结了这么几条，大家听一听是不是这么个道理。&lt;/p>
&lt;h2 id="添加-foreach-方法的后果">
添加 ForEach 方法的后果
&lt;a href="#%e6%b7%bb%e5%8a%a0-foreach-%e6%96%b9%e6%b3%95%e7%9a%84%e5%90%8e%e6%9e%9c" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;h3 id="破坏接口的纯粹性">
破坏接口的纯粹性
&lt;a href="#%e7%a0%b4%e5%9d%8f%e6%8e%a5%e5%8f%a3%e7%9a%84%e7%ba%af%e7%b2%b9%e6%80%a7" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>这句话如果说得再“专业”一点，就是违背了 SOLID 原则中的“单一职责原则”（Single responsibility principle）以及“接口隔离原则”（Interface segregation principle）。&lt;/p>
&lt;p>怎么讲？我们可以看一下 &lt;code>IEnumerable&lt;/code> 接口的定义：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="k">namespace&lt;/span> &lt;span class="nn">System.Collections.Generic&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">interface&lt;/span> &lt;span class="nc">IEnumerable&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="k">out&lt;/span> &lt;span class="n">T&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">IEnumerable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="n">IEnumerator&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">GetEnumerator&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>非常地干净。就连它的名字也表明了，它只是表明一个对象拥有“被枚举”的能力。但是 &lt;code>ForEach&lt;/code> 方法通常会伴随着一些执行逻辑，这可能就与接口的初衷不符了。&lt;/p>
&lt;p>不同于 LINQ 中的 &lt;code>Select&lt;/code>、&lt;code>Where&lt;/code>、&lt;code>OrderBy&lt;/code> 等，它们都是对于数据的映射、筛选、排序等，通常不会包含什么逻辑操作。试想，如果 LINQ 中包含了逻辑操作，尤其还是耗时的操作（比如占用 CPU 的复杂计算、IO 操作等）时，LINQ 的使用将会变得不那么可靠。&lt;/p>
&lt;p>所以，假如 &lt;code>ForEach&lt;/code> 方法中也存在这样的耗时操作，我们更应该考虑的做法是使用异步编程（比如创建多个异步任务，然后使用 &lt;code>Task.WhenAll&lt;/code> 方法进行等待），使用 &lt;code>Parallel&lt;/code> 类或者 PLINQ 等，从而使得我们的 &lt;code>IEnumerable&lt;/code> 对象只包含数据序列，不包含不可控的操作逻辑。&lt;/p>
&lt;p>这里给出一个简单的使用异步的例子：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="n">IEnumerable&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">items&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">...;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">tasks&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Select&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">CalculateValueAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">await&lt;/span> &lt;span class="n">Task&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WhenAll&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tasks&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">results&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">tasks&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Select&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">t&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Result&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">ToList&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="方法带来的副作用">
方法带来的副作用
&lt;a href="#%e6%96%b9%e6%b3%95%e5%b8%a6%e6%9d%a5%e7%9a%84%e5%89%af%e4%bd%9c%e7%94%a8" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>诚然，我们可以自己写一个扩展方法，从而让 &lt;code>IEnumerable&lt;/code> 对象能够像 &lt;code>List&lt;/code> 那样使用 &lt;code>ForEach&lt;/code> 方法：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">EnumerableExtensions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">ForEach&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="k">this&lt;/span> &lt;span class="n">IEnumerable&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">items&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Action&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">action&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">items&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">action&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Invoke&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但这可能会造成对 LINQ 现有功能的污染。为什么这么说呢？&lt;/p>
&lt;p>我们来想一想，LINQ 提供的功能主要是做什么的？其实主要是对于数据的映射、筛选、排序等。这些方法通常都被认为不会对原始数据造成影响，或者修改。虽然这些方法也会接收一个回调，但是这个回调一定是个 &lt;code>Func&lt;/code>，从而返回映射后的对象、筛选及排序的依据等。&lt;/p>
&lt;p>但是 &lt;code>ForEach&lt;/code> 方法则不同，它接收的是一个 &lt;code>Action&lt;/code>，那就是说这个回调并不需要返回什么。此时我们的操作通常就有可能会对原始数据产生影响了。比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Employee&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">employees&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">...;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">employees&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ForEach&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IsPromoted&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 如果员工晋升，则涨薪&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">e&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Salary&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="m">1000&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>所以，使用 &lt;code>ForEach&lt;/code> 时我们是倾向于对于原本的数据进行一定的操作的。&lt;/p>
&lt;p>当然这里仅仅表示一种推测，实际的用法并不绝对。即便我们使用 LINQ 中的 &lt;code>Select&lt;/code> 等方法，也同样是可以做到对于数据的修改的，这一点 LINQ 并没有办法阻止我们。所以这里主要还是一个“轻重”关系。相较于 LINQ 的方法，&lt;code>ForEach&lt;/code> 是更倾向于会对数据进行操作的。&lt;/p>
&lt;h3 id="性能和资源等方面的考虑">
性能和资源等方面的考虑
&lt;a href="#%e6%80%a7%e8%83%bd%e5%92%8c%e8%b5%84%e6%ba%90%e7%ad%89%e6%96%b9%e9%9d%a2%e7%9a%84%e8%80%83%e8%99%91" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>还有一个点，就是 拥有 &lt;code>ForEach&lt;/code> 方法的 &lt;code>List&lt;/code>（类似的还有 &lt;code>Array.ForEach&lt;/code> 静态方法），它们被操作的对象都有一个显著特点：元素数量是已知（或者说有限）的。这一点非常重要，因为一个 &lt;code>IEnumerable&lt;/code> 对象完全有可能是无限的！&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-c#" data-lang="c#">&lt;span class="line">&lt;span class="cl">&lt;span class="n">IEnumerable&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">GenerateNumbers&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>从这个角度考虑，对一个容量未知的集合轻易开展 &lt;code>ForEach&lt;/code> 这样的操作，其实是充满风险的。不仅如此，&lt;code>ForEach&lt;/code> 方法不像是 &lt;code>foreach&lt;/code> 语句那样，可以在其中书写 &lt;code>continue&lt;/code>、&lt;code>break&lt;/code> 或 &lt;code>return&lt;/code> 等语句，这就意味着它一旦开始，就只能将整个集合中的全部元素逐个来一遍才行了。&lt;/p>
&lt;p>所以从这个角度考虑，不给 &lt;code>IEnumerable&lt;/code> 对象提供这样的扩展方法，似乎是非常有道理的。当然，你依旧可以说，LINQ 的方法遇到这样的极端情况，同样束手无策呀？的确，但 LINQ 针对的就是 &lt;code>IEnumerable&lt;/code> 类型，这是无法避免的，只能希望开发者清楚自己面对的集合是有限的还是无限的。&lt;/p>
&lt;p>&lt;strong>＜2024 年 5 月 7 日更新＞&lt;/strong>&lt;/p>
&lt;p>最近 Nick Chapsas 在他的&lt;a class="link" href="https://www.youtube.com/watch?v=0iTMIxZeyXg" target="_blank" rel="noopener"
>一期视频&lt;/a>中讨论了 &lt;code>ForEach&lt;/code> 方法的性能。通过 Benchmark（在视频的约 5:45 处）可以看出，它的性能是显著低于传统的 &lt;code>for&lt;/code> 以及 &lt;code>foreach&lt;/code> 的：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">| Method | Mean | Allocation |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| ------- | ---------- | ---------- |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| for | 424.9 us | - |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| foreach | 426.4 us | - |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| ForEach | 1,785.0 us | 88 B |
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">&lt;p>关于上面的表格，有一些需要额外补充的内容：&lt;/p>
&lt;ol>
&lt;li>上面的表格省去了一些列，只保留了主要部分&lt;/li>
&lt;li>测试环境是最新的 .NET 9（预览版），所以 &lt;code>foreach&lt;/code> 与 &lt;code>for&lt;/code> 拥有近乎一样的性能，且没有内存开销&lt;/li>
&lt;li>&lt;code>ForEach&lt;/code> 速度慢了约 4 倍，且拥有内存开销（因为存在委托和相应的闭包）&lt;/li>
&lt;/ol>&lt;/div>
&lt;/div>
&lt;p>所以这更加证明了，&lt;code>ForEach&lt;/code> 方法并不是一个高性能的方法，如果我们需要对一个集合进行遍历，还是应该使用传统的 &lt;code>for&lt;/code> 或 &lt;code>foreach&lt;/code>。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>总的来说，虽然为 &lt;code>IEnumerable&amp;lt;T&amp;gt;&lt;/code> 添加一个 &lt;code>ForEach&lt;/code> 方法在技术上是可行的，但由于设计哲学、清晰的代码维护、性能考虑和潜在的副作用，.NET 框架设计者选择不在 &lt;code>IEnumerable&amp;lt;T&amp;gt;&lt;/code> 中直接提供这样的方法。不过，大家如果需要，可以自定义扩展方法来实现这一功能。但前提是要清楚这样做可能会带来的后果。&lt;/p></description></item><item><title>为什么我们需要 ValueTask？</title><link>https://blog.coldwind.top/posts/why-we-need-valuetask/</link><pubDate>Fri, 12 Apr 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/why-we-need-valuetask/</guid><description>&lt;img src="https://s2.loli.net/2024/04/14/P4HJMlIpSxY6CDn.jpg" alt="Featured image of post 为什么我们需要 ValueTask？" />&lt;blockquote>
&lt;p>本文有对应的视频教程：&lt;a class="link" href="https://www.bilibili.com/video/BV1dm421j72Y/" target="_blank" rel="noopener"
>哔哩哔哩&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>自从 C# 5.0 引入了 &lt;code>async&lt;/code> 和 &lt;code>await&lt;/code> 语法以后，异步编程变得非常简单，而 Task 类型也在开发中扮演着相当重要的角色，存在感极高。但是在 .NET Core 2.0 这个版本，微软引入了一个新的类型 &lt;code>ValueTask&lt;/code>，那么这个类型是什么？为什么我们需要它？什么情况下应该使用它？我们今天就来探讨一下。&lt;/p>
&lt;h2 id="简单回顾-task-类型">
简单回顾 &lt;code>Task&lt;/code> 类型
&lt;a href="#%e7%ae%80%e5%8d%95%e5%9b%9e%e9%a1%be-task-%e7%b1%bb%e5%9e%8b" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在异步编程中，我们经常会使用 &lt;code>Task&lt;/code> 类型来表示一个异步操作或者说异步任务。相较于其他一些主流编程语言，C# 中的异步任务其实开销很小。比如知乎上的大佬 hez2010 在他的&lt;a class="link" href="https://www.zhihu.com/question/509501955/answer/3225113571" target="_blank" rel="noopener"
>这个回答&lt;/a>中提到，C# 的 Task 类型通常只占用 64~136 B 的内存，而 Go 语言的一个 goroutine 至少占用 2 KB 的内存。&lt;/p>
&lt;p>不仅如此，Task 还有许多优化技巧，比如：&lt;/p>
&lt;ol>
&lt;li>如果想直接返回一个结果，可以使用 &lt;code>Task.FromResult&lt;/code> 方法&lt;/li>
&lt;li>如果想直接返回一个已经完成的任务，可以使用 &lt;code>Task.CompletedTask&lt;/code>&lt;/li>
&lt;li>如果想直接返回一个已经取消的任务，可以使用 &lt;code>Task.FromCanceled&lt;/code>&lt;/li>
&lt;li>如果想直接返回一个已经失败的任务，可以使用 &lt;code>Task.FromException&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>等等。所以从 C# 5.0（大概是 .NET Framework 4 时代）开始，直到 .NET Core 2.0 之前，一直相安无事。&lt;/p>
&lt;h2 id="传统-task-类型的问题">
传统 &lt;code>Task&lt;/code> 类型的问题
&lt;a href="#%e4%bc%a0%e7%bb%9f-task-%e7%b1%bb%e5%9e%8b%e7%9a%84%e9%97%ae%e9%a2%98" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>但是，随着 .NET 开始跨平台，能使用 C# 的场景越来越多，微软的“野心”也越来越大，开始从各种角度优化 C# 的性能，从而使 .NET 能够胜任各种任务场景。除了引入 &lt;code>Span&lt;/code>、&lt;code>Memory&lt;/code>、&lt;code>ref struct&lt;/code> 等新特性外，还引入了 &lt;code>ValueTask&lt;/code>。那么，传统的 &lt;code>Task&lt;/code> 类型有什么问题呢？&lt;/p>
&lt;p>首先我们要知道，&lt;code>Task&lt;/code> 包含泛型版本和非泛型版本，分别对应有无返回值的异步任务。而 &lt;code>ValueTask&lt;/code> 在诞生之初，只有一个泛型版本。换句话说，设计者认为，&lt;code>ValueTask&lt;/code> 应当只适用于有返回值的异步任务。所以这里我们来看一个典型的例子：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">ConcurrentDictionary&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">_cache&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">async&lt;/span> &lt;span class="n">Task&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">GetMessageAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">TryGetValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">out&lt;/span> &lt;span class="kt">var&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">message&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">GetMessageFromDatabaseAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_cache&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">TryAdd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>在上面的 &lt;code>GetMessageAsync&lt;/code> 方法中，我们首先尝试从缓存中获取消息，如果没有找到，就再尝试从数据库中获取。但这里有一个问题，如果缓存中有数据，那么虽然我们好像会直接返回一个值。但是，由于 &lt;code>GetMessageAsync&lt;/code> 方法是一个异步方法，所以实际上会返回一个 &lt;code>Task&amp;lt;string&amp;gt;&lt;/code> 类型的对象。这就意味着，即便我们本可以只返回一个值，我们依旧会多创建一个 &lt;code>Task&lt;/code> 对象，这就导致了无端的内存开销。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">这种在异步任务中直接返回一个值的情况，我们称之为“同步完成”，或者“返回同步结果”。线程进入这个异步任务后，并没有碰到 &lt;code>await&lt;/code> 关键字，而是直接返回。也就是说，这个异步任务自始至终都是在同一个线程上执行的。&lt;/div>
&lt;/div>
&lt;h2 id="valuetask-简介">
&lt;code>ValueTask&lt;/code> 简介
&lt;a href="#valuetask-%e7%ae%80%e4%bb%8b" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>所以，&lt;code>ValueTask&lt;/code> 的主要作用就是解决这个问题。它在 .NET Core 2.0 被正式引入，并在 .NET Core 2.1 得到了增强（新增了 &lt;code>IValueTaskSource&amp;lt;T&amp;gt;&lt;/code> 接口，从而使它可以拥有诸如 &lt;code>IsCompleted&lt;/code> 等属性），并且还添加了非泛型的 &lt;code>ValueTask&lt;/code> 类型（这个我们稍后再说）。&lt;/p>
&lt;p>&lt;code>ValueTask&lt;/code> 我们先不要去思考它是否为值类型，而是可以这么理解：&lt;strong>它适用于可能返回一个 &lt;code>Value&lt;/code>，也可能返回一个 &lt;code>Task&lt;/code> 的情形&lt;/strong>。也就是说，它非常适合上面的“缓存命中”的典型场景。我们可以把上面的代码修改为：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kd">async&lt;/span> &lt;span class="n">ValueTask&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">GetMessageAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">TryGetValue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">out&lt;/span> &lt;span class="kt">var&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">message&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">GetMessageFromDatabaseAsync&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">_cache&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">TryAdd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">message&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>此时，如果缓存中有数据，那么我们可以直接返回一个 &lt;code>ValueTask&amp;lt;T&amp;gt;&lt;/code> 对象，而不需要再创建一个 &lt;code>Task&amp;lt;T&amp;gt;&lt;/code> 对象。这样就避免了无端的堆内存开销；否则，我们才会创建 &lt;code>Task&amp;lt;T&amp;gt;&lt;/code> 对象。或者说，在这种情况下，&lt;code>ValueTask&lt;/code> 的性能会退化为 &lt;code>Task&lt;/code>（甚至可能还稍微低一丁点，因为涉及到更多的字段，以及值拷贝等）。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">至于非泛型版本的 &lt;code>ValueTask&lt;/code>，它的使用情形就更少了。它只有在即使异步完成也可以无需分配内存的情况下才会派上用场。&lt;code>ValueTask&lt;/code> 的“发明者”Stephen Toub 在&lt;a class="link" href="https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/" target="_blank" rel="noopener"
>他的文章&lt;/a>中提到，除非你借助 profiling 工具确认 &lt;code>Task&lt;/code> 的这一丁点开销会成为瓶颈，否则不需要考虑使用 &lt;code>ValueTask&lt;/code>。&lt;/div>
&lt;/div>
&lt;p>这时候我们再来思考它的性能究竟如何：&lt;/p>
&lt;p>顾名思义，&lt;code>ValueTask&lt;/code> 是一个值类型，可以在栈上分配，而不需要在堆上分配。不仅如此，它因为实现了一些接口，从而使它可以像 &lt;code>Task&lt;/code> 一样被用于异步编程。所以，照理说，&lt;code>ValueTask&lt;/code> 的性能要比 &lt;code>Task&lt;/code> 更好很多（就如同 &lt;code>ValueTuple&lt;/code> 之于 &lt;code>Tuple&lt;/code>、&lt;code>Span&lt;/code> 之于 &lt;code>Array&lt;/code> 一样）。&lt;/p>
&lt;p>但是，&lt;code>ValueTask&lt;/code> 真的这么美好吗？它是不是可以完全替代 &lt;code>Task&lt;/code> 呢？事情恐怕并没有这么简单。&lt;/p>
&lt;h2 id="valuetask-的注意事项">
&lt;code>ValueTask&lt;/code> 的注意事项
&lt;a href="#valuetask-%e7%9a%84%e6%b3%a8%e6%84%8f%e4%ba%8b%e9%a1%b9" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>现在，我们该谈一谈 &lt;code>ValueTask&lt;/code> 在使用时需要注意的地方了。&lt;/p>
&lt;h3 id="valuetask-不能被多次等待await">
&lt;code>ValueTask&lt;/code> 不能被多次等待（&lt;code>await&lt;/code>）
&lt;a href="#valuetask-%e4%b8%8d%e8%83%bd%e8%a2%ab%e5%a4%9a%e6%ac%a1%e7%ad%89%e5%be%85await" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>&lt;code>ValueTask&lt;/code> 底层会使用一个对象存储异步操作的状态，而它在被 &lt;code>await&lt;/code> 后（可以认为此时异步操作已经结束），这个对象可能已经被回收，甚至有可能已经被用在别处（或者说，&lt;code>ValueTask&lt;/code> 可能会从已完成状态变成未完成状态）。而 &lt;code>Task&lt;/code> 是绝对不可能发生这种情况的，所以可以被多次等待。&lt;/p>
&lt;h3 id="不要阻塞-valuetask">
不要阻塞 &lt;code>ValueTask&lt;/code>
&lt;a href="#%e4%b8%8d%e8%a6%81%e9%98%bb%e5%a1%9e-valuetask" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>&lt;code>ValueTask&lt;/code> 所对应的 &lt;code>IValueTaskSource&lt;/code> 并不需要支持在任务未完成时阻塞的功能，并且通常也不会这样做。这意味着，你无法像使用 &lt;code>Task&lt;/code> 那样在 &lt;code>ValueTask&lt;/code> 上调用 &lt;code>Wait&lt;/code>、&lt;code>Result&lt;/code>、&lt;code>GetAwaiter().GetResult()&lt;/code> 等方法。&lt;/p>
&lt;p>但换句话说，如果你可以确定一个 &lt;code>ValueTask&lt;/code> 已经完成（通过判断 &lt;code>IsCompleted&lt;/code> 等属性的值），那么你可以通过 &lt;code>Result&lt;/code> 属性来安全地获取 &lt;code>ValueTask&lt;/code> 的结果。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">微软专门添加了一个与这个有关的警告：&lt;a class="link" href="https://learn.microsoft.com/zh-cn/dotnet/fundamentals/code-analysis/quality-rules/ca2012" target="_blank" rel="noopener"
>CA2012&lt;/a>&lt;/div>
&lt;/div>
&lt;h3 id="不要在多个线程上同时等待一个-valuetask">
不要在多个线程上同时等待一个 &lt;code>ValueTask&lt;/code>
&lt;a href="#%e4%b8%8d%e8%a6%81%e5%9c%a8%e5%a4%9a%e4%b8%aa%e7%ba%bf%e7%a8%8b%e4%b8%8a%e5%90%8c%e6%97%b6%e7%ad%89%e5%be%85%e4%b8%80%e4%b8%aa-valuetask" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>&lt;code>ValueTask&lt;/code> 在设计之初就只是用来解决 &lt;code>Task&lt;/code> 在个别情况下的开销问题，而不是打算全面取代 &lt;code>Task&lt;/code>。因此，&lt;code>Task&lt;/code> 的很多优秀且便捷的特性它都不用有。其中一个就是线程安全的等待。&lt;/p>
&lt;p>也就是说，&lt;code>ValueTask&lt;/code> 底层的对象被设计为只希望被一个消费者（或线程）等待，因此并没有引入线程安全等机制。尝试同时等待它可能很容易引入竞态条件和微妙的程序错误。而 &lt;code>Task&lt;/code> 支持任意数量的并发等待。&lt;/p>
&lt;h2 id="如何克服-valuetask-的局限性">
如何克服 &lt;code>ValueTask&lt;/code> 的局限性
&lt;a href="#%e5%a6%82%e4%bd%95%e5%85%8b%e6%9c%8d-valuetask-%e7%9a%84%e5%b1%80%e9%99%90%e6%80%a7" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在实际使用过程中，难免遇到需要突破它的上述限制的情况。那么我们该怎么办呢？这里给出几种常见情况的对应方式：&lt;/p>
&lt;ol>
&lt;li>如果希望用阻塞的方式（&lt;code>Result&lt;/code> 与 &lt;code>.GetAwaiter().GetResult()&lt;/code>）获取 &lt;code>ValueTask&amp;lt;T&amp;gt;&lt;/code> 的结果，可以先判断 &lt;code>IsCompleted&lt;/code> 或 &lt;code>IsCompletedSuccessfully&lt;/code> 等属性的值，确认它已经完成，然后再获取结果&lt;/li>
&lt;li>如果希望等待多次，或在多个线程中等待等，那么可以使用 &lt;code>AsTask()&lt;/code> 方法将其转为一个普通的 &lt;code>Task&lt;/code>，进而再进行各种 &lt;code>Task&lt;/code> 的常用操作&lt;/li>
&lt;/ol>
&lt;p>基于 &lt;code>ValueTask&lt;/code> 的原理及限制，一个普遍认同的推荐用法是：&lt;/p>
&lt;div class="notice tip">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-lightbulb" aria-hidden="true">&lt;/i>Tip
&lt;/div>
&lt;div class="notice-content">绝大多数情况下，都推荐直接使用 &lt;code>await&lt;/code> 关键字来等待一个返回值为 &lt;code>ValueTask&amp;lt;T&amp;gt;&lt;/code> 的异步任务并获取结果，&lt;strong>而不是试图将其返回值赋值给一个变量&lt;/strong>（最多是搭配 &lt;code>ConfigureAwait()&lt;/code> 进行使用）；否则，建议使用 &lt;code>AsTask()&lt;/code> 方法将其转为传统的 &lt;code>Task&lt;/code>，再进行常规操作。&lt;/div>
&lt;/div>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>总的来说，&lt;code>ValueTask&lt;/code> 确实有很多闪光点，比如在栈上分配来避免堆分配的性能开销，但它也有一些让人头疼的限制，比如不能被多次等待。使用它就像是在走钢丝，一不小心就可能掉进性能优化的陷阱里。但别担心，大多数情况下，我们还是可以安全地使用 &lt;code>await&lt;/code> 来等待 &lt;code>ValueTask&amp;lt;T&amp;gt;&lt;/code> 的，只要我们不试图把它当作 &lt;code>Task&lt;/code> 的替代品来用就好。&lt;/p>
&lt;p>希望看了这篇文章之后，大家能够正确使用 &lt;code>ValueTask&lt;/code>。&lt;/p>
&lt;h2 id="参考链接">
参考链接
&lt;a href="#%e5%8f%82%e8%80%83%e9%93%be%e6%8e%a5" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;ul>
&lt;li>&lt;a class="link" href="https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs,77a292425839ae85" target="_blank" rel="noopener"
>ValueTask Source Code&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/" target="_blank" rel="noopener"
>Understanding the Whys, Whats, and Whens of ValueTask | .NET Blog&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://www.codeguru.com/csharp/c-sharp-valuetask/" target="_blank" rel="noopener"
>Working with ValueTask in C# | CodeGuru.com&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://www.youtube.com/watch?v=dCj7-KvaIJ0" target="_blank" rel="noopener"
>Task vs ValueTask: When Should I use ValueTask? | YouTube.com&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://www.youtube.com/watch?v=fj-LVS8hqIE" target="_blank" rel="noopener"
>Understanding how to use Task and ValueTask | YouTube.com&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Using 语句的陷阱</title><link>https://blog.coldwind.top/posts/using-statement-trap/</link><pubDate>Thu, 11 Apr 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/using-statement-trap/</guid><description>&lt;img src="https://s2.loli.net/2024/04/11/lbxw86NGjKJyqAv.jpg" alt="Featured image of post Using 语句的陷阱" />&lt;p>&lt;code>using&lt;/code> 语句在 C# 中有很多种用法，比如引入命名空间，为类型起别名，或者释放资源等。这篇文章我们主要讨论 &lt;code>using&lt;/code> 语句在释放资源时的陷阱。&lt;/p>
&lt;h2 id="using-以前的使用方式">
using 以前的使用方式
&lt;a href="#using-%e4%bb%a5%e5%89%8d%e7%9a%84%e4%bd%bf%e7%94%a8%e6%96%b9%e5%bc%8f" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在很久很久以前，我们如果想要读取一个外部文本文件的内容，可能会这样写（不考虑更简洁易用的 &lt;code>File.ReadAllText()&lt;/code> 等方法）：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">stream&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">FileStream&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">FileMode&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Open&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">using&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">reader&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">StreamReader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">content&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">reader&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ReadToEnd&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>其实上面的代码，是可以减少一层缩进的，并且这也是各种 IDE 推荐的写法，形如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">stream&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">FileStream&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">FileMode&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Open&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">reader&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">StreamReader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">content&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">reader&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ReadToEnd&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这个其实很有意思，因为一般我们都认为，即便外层的语句省略了花括号，内层的语句依旧会保持缩进，就比如多层 &lt;code>if&lt;/code> 语句：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">condition1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">condition2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// do something&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>但是上面展示的 &lt;code>using&lt;/code> 的省略外层花括号的新语法，内层的语句并不会额外添加缩进，而是会与外层保持同一层级。不信的话，可以使用任意一个格式化工具，比如 Visual Studio 的 &lt;code>Ctrl+K, Ctrl+D&lt;/code>，格式化一下上面的代码，看看会是什么样子。&lt;/p>
&lt;h2 id="using-的新语法">
using 的新语法
&lt;a href="#using-%e7%9a%84%e6%96%b0%e8%af%ad%e6%b3%95" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>C# 8.0 为我们带来了一个新的 &lt;code>using&lt;/code> 语句的用法，可以减少一层缩进，让代码看起来更简洁。同样是上面的代码，现在可以写成：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">string&lt;/span> &lt;span class="n">filename&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;test.txt&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">stream&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">FileStream&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">filename&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">FileMode&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Open&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">reader&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">StreamReader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">content&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">reader&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ReadToEnd&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>上面的代码实际上会被编译为这样的 low-level C# 代码：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">string&lt;/span> &lt;span class="n">path&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;test.txt&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">FileStream&lt;/span> &lt;span class="n">fileStream&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">FileStream&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">FileMode&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Open&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">StreamReader&lt;/span> &lt;span class="n">streamReader&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">StreamReader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fileStream&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">string&lt;/span> &lt;span class="k">value&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">streamReader&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ReadToEnd&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">finally&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">streamReader&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="n">IDisposable&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">streamReader&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">Dispose&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">finally&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">fileStream&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="n">IDisposable&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">fileStream&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">Dispose&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>不难看出，&lt;code>using&lt;/code> 语句用于资源释放时，其实是通过 &lt;code>try-finally&lt;/code> 语句来实现的。当存在多层的 &lt;code>using&lt;/code> 语句时，每一层都会对应一个 &lt;code>try-finally&lt;/code> 语句，也就变成了上面的样子。&lt;/p>
&lt;p>新的语法会将 &lt;code>using&lt;/code> 语句下面的内容（准确地说，是当前作用域中剩下的代码）包装在 &lt;code>try-finally&lt;/code> 语句中，从而保证代码在离开作用域前，会释放资源。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">仔细观察还可以发现，&lt;code>Dispose&lt;/code> 的顺序是从内到外的，或者说先被 &lt;code>using&lt;/code> 的对象会后被释放。&lt;/div>
&lt;/div>
&lt;h2 id="新语法的陷阱">
新语法的陷阱
&lt;a href="#%e6%96%b0%e8%af%ad%e6%b3%95%e7%9a%84%e9%99%b7%e9%98%b1" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>学了这个新语法之后，相信很多人都打算全面替代掉旧方法，毕竟少写了花括号，而且减少了缩进，效果还一模一样。但实际上，这种新语法并不是适用于所有情况的。也就是说，效果未必一模一样。比如之前我就踩了一个坑。&lt;/p>
&lt;p>当时的情况是，我在使用 &lt;code>System.IO.Compression&lt;/code> 命名空间下的 &lt;code>GZipStream&lt;/code> 来压缩一个文本，并输出压缩后的内容。我使用的代码如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.IO.Compression&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Text&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">string&lt;/span> &lt;span class="n">input&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;text to be compressed.&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">outputStream&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MemoryStream&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">inputStream&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MemoryStream&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Encoding&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">UTF8&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetBytes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">input&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">compressor&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">GZipStream&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">outputStream&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CompressionLevel&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Optimal&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">inputStream&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CopyTo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">compressor&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">compressed&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">outputStream&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToArray&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">compressed&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>运行后，输出了压缩后的内容长度为 &lt;code>10&lt;/code>。&lt;/p>
&lt;p>但是当我修改了 &lt;code>input&lt;/code> 的字符串内容后，发现输出的长度依旧是 &lt;code>10&lt;/code>，这显然是不可能的。我检查了一下代码，发现问题出在了 &lt;code>using&lt;/code> 语句上。只要把上面的代码修改成这样，就能得到正确的结果：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">outputStream&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MemoryStream&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">inputStream&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MemoryStream&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Encoding&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">UTF8&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetBytes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">input&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">compressor&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">GZipStream&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">outputStream&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CompressionLevel&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Optimal&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">inputStream&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">CopyTo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">compressor&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">compressed&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">outputStream&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToArray&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">compressed&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>造成这一现象的原因是，如果想要得到正确的压缩后的内容，需要保证 &lt;code>GZipStream&lt;/code> 已经被释放。但是如果我们不加声明 &lt;code>GZipStream&lt;/code> 这一行的花括号，会导致它直到离开作用域时才被释放，而不是在 &lt;code>inputStream.CopyTo(compressor)&lt;/code> 之后立即释放。&lt;/p>
&lt;p>所以，大家在使用新的 &lt;code>using&lt;/code> 语句时，一定要根据实际情况来判断是否适用，不要无脑替换掉以前的旧写法。&lt;/p></description></item><item><title>C# 鸭子类型汇总</title><link>https://blog.coldwind.top/posts/csharp-duck-types/</link><pubDate>Thu, 04 Apr 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/csharp-duck-types/</guid><description>&lt;img src="https://s2.loli.net/2024/04/11/SIRUGn7OflgEWsr.jpg" alt="Featured image of post C# 鸭子类型汇总" />&lt;h2 id="什么是鸭子类型">
什么是“鸭子类型”？
&lt;a href="#%e4%bb%80%e4%b9%88%e6%98%af%e9%b8%ad%e5%ad%90%e7%b1%bb%e5%9e%8b" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>鸭子类型的名字来源于一句俚语：&lt;/p>
&lt;blockquote>
&lt;p>如果它走起来像鸭子，叫起来像鸭子，那么它就是鸭子。&lt;/p>
&lt;/blockquote>
&lt;p>这句话的意思是，如果一个对象具有某个方法或属性，那么它就可以被当作拥有这个方法或属性的类型来使用，而不需要严格地遵循一些规定与要求。&lt;/p>
&lt;p>在 C# 中，通常我们会认为，如果想要使用一些语法，需要实现一些接口。比如你很可能会觉得：&lt;/p>
&lt;ul>
&lt;li>如果想要使用 &lt;code>foreach&lt;/code> 语句，需要实现 &lt;code>IEnumerable&lt;/code> 接口&lt;/li>
&lt;li>如果想要使用 &lt;code>await&lt;/code> 语句，需要与 &lt;code>Task&lt;/code> 类或一些底层接口扯上关系&lt;/li>
&lt;li>如果想要使用 &lt;code>using&lt;/code> 语句释放资源，需要实现 &lt;code>IDisposable&lt;/code> 接口&lt;/li>
&lt;/ul>
&lt;p>实际上真的如此吗？这篇文章我们就来总结一下 C# 中的那些不为人知的鸭子类型。&lt;/p>
&lt;h2 id="c-中的鸭子类型">
C# 中的鸭子类型
&lt;a href="#c-%e4%b8%ad%e7%9a%84%e9%b8%ad%e5%ad%90%e7%b1%bb%e5%9e%8b" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;h3 id="foreach-语句">
&lt;code>foreach&lt;/code> 语句
&lt;a href="#foreach-%e8%af%ad%e5%8f%a5" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>C# 标准库为我们提供了大量的集合类型，比如 &lt;code>List&lt;/code>、&lt;code>Stack&lt;/code>、&lt;code>Queue&lt;/code>、&lt;code>ObservableCollection&lt;/code> 等等。这些集合类型都实现了 &lt;code>IEnumerable&lt;/code> 接口，所以我们可以使用 &lt;code>foreach&lt;/code> 语法来遍历它们。&lt;/p>
&lt;p>但实际上，&lt;code>foreach&lt;/code> 语法并不要求类必须实现 &lt;code>IEnumerable&lt;/code> 接口。只要类中有一个名为 &lt;code>GetEnumerator&lt;/code> 的方法，返回一个 &lt;code>IEnumerator&lt;/code> 类型的对象，就可以使用 &lt;code>foreach&lt;/code> 语法。比如下面这个例子：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">c&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MyEnumerableClass&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyEnumerableClass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">items&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">3&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">IEnumerator&lt;/span> &lt;span class="n">GetEnumerator&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetEnumerator&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我们还可以玩一些“花活”。事实上，这个 &lt;code>GetEnumerator&lt;/code> 方法其实都不需要直接出自这个类型，甚至可以是一个扩展方法。所以，我们可以对一些我们无法修改的类添加扩展方法，从而使它们变得可以被 &lt;code>foreach&lt;/code> 语法遍历。比如 C# 8.0 引入了 &lt;code>Range&lt;/code> 与 &lt;code>Index&lt;/code> 两个类型，表示数组的范围与索引。&lt;/p>
&lt;div class="notice info">
&lt;div class="notice-title">
&lt;i class="fa-solid fa-exclamation-circle" aria-hidden="true">&lt;/i>Info
&lt;/div>
&lt;div class="notice-content">&lt;p>通常我们不会直接声明一个 &lt;code>Range&lt;/code> 对象，而是会使用形如 &lt;code>array[1..5]&lt;/code> 这样的语法来表示一个范围。在底层，这个 &lt;code>1..5&lt;/code> 会被转换为一个 &lt;code>Range&lt;/code> 对象。&lt;/p>
&lt;p>你甚至可以不在数组的索引器中使用这个语法，而是直接声明一个变量并这样进行赋值：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">range&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">1.&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">range&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetType&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">Name&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// Range&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/div>
&lt;/div>
&lt;p>我们可以为 &lt;code>Range&lt;/code> 类型添加一个扩展方法，使得它们可以被 &lt;code>foreach&lt;/code> 语法遍历：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MyExtensions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="n">IEnumerator&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">GetEnumerator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span> &lt;span class="n">Range&lt;/span> &lt;span class="n">range&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">range&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Start&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Value&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">&amp;lt;=&lt;/span> &lt;span class="n">range&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">End&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Value&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后就可以这样玩了：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="m">1.&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="m">3&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 1..3 是一个 Range 类型&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 输出：&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 3&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="await-语句">
&lt;code>await&lt;/code> 语句
&lt;a href="#await-%e8%af%ad%e5%8f%a5" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>类似地，&lt;code>await&lt;/code> 语法也不要求类必须继承 &lt;code>Task&lt;/code> 类或实现一些底层接口。只要类中有一个名为 &lt;code>GetAwaiter&lt;/code> 的方法，返回一个 &lt;code>IAwaiter&lt;/code> 类型的对象，就可以使用 &lt;code>await&lt;/code> 语法。并且与上面 &lt;code>foreach&lt;/code> 的例子相同，我们也可以把 &lt;code>GetAwaiter&lt;/code> 方法定义为一个扩展方法，从而“扩展”一些我们无法修改的类。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Extensions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="n">TaskAwaiter&lt;/span> &lt;span class="n">GetAwaiter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span> &lt;span class="n">TimeSpan&lt;/span> &lt;span class="n">ts&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Task&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Delay&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ts&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">GetAwaiter&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="n">TaskAwaiter&lt;/span> &lt;span class="n">GetAwaiter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">this&lt;/span> &lt;span class="kt">double&lt;/span> &lt;span class="n">sec&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Task&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Delay&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">TimeSpan&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FromSeconds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sec&lt;/span>&lt;span class="p">)).&lt;/span>&lt;span class="n">GetAwaiter&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>上面的静态类中声明了两个扩展方法，分别为 &lt;code>TimeSpan&lt;/code> 与 &lt;code>double&lt;/code> 类型添加了 &lt;code>GetAwaiter&lt;/code> 方法。然后我们就可以这样使用 &lt;code>await&lt;/code> 语法了：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">await&lt;/span> &lt;span class="n">TimeSpan&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">FromSeconds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">await&lt;/span> &lt;span class="m">1.0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>是不是感觉越来越离谱了？不过实际开发中，轻易还是不要使用这样的技巧，因为这会严重污染常用的类型，可以说是有百害而无一利。&lt;/p>
&lt;h3 id="using-语句">
&lt;code>using&lt;/code> 语句
&lt;a href="#using-%e8%af%ad%e5%8f%a5" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>如果你认为 &lt;code>using&lt;/code> 语句只能用于实现了 &lt;code>IDisposable&lt;/code> 接口的类，那你终于基本上对了一次😂。的确，对于一个 &lt;code>class&lt;/code> 类型的对象，如果它没有实现 &lt;code>IDisposable&lt;/code> 接口，那么即便它拥有 &lt;code>public void Dispose()&lt;/code> 方法，它仍然是无法使用 &lt;code>using&lt;/code> 语句的（编译器会提示，这个对象必须可以隐式转换为 &lt;code>IDisposable&lt;/code> 对象）。&lt;/p>
&lt;p>但是！&lt;/p>
&lt;p>C# 中还有一个不太常用的 &lt;code>ref struct&lt;/code> 类型。这种类型的对象在离开作用域时会自动被销毁，所以它们不需要实现 &lt;code>IDisposable&lt;/code> 接口。即便如此，我们可以为这种类型的对象添加一个 &lt;code>Dispose&lt;/code> 方法，这样我们就可以使用 &lt;code>using&lt;/code> 语句来释放资源了。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">ref&lt;/span> &lt;span class="k">struct&lt;/span> &lt;span class="nc">MyDisposableStructType&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Dispose&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;MyDisposableStructType Disposed.&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">var&lt;/span> &lt;span class="n">s&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">MyDisposableStructType&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这样的话，我们就可以对一个没有实现 &lt;code>IDisposable&lt;/code> 接口的对象使用 &lt;code>using&lt;/code> 语句了。&lt;/p>
&lt;h3 id="集合初始化器">
集合初始化器
&lt;a href="#%e9%9b%86%e5%90%88%e5%88%9d%e5%a7%8b%e5%8c%96%e5%99%a8" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>C# 中的很多集合类型都支持集合初始化器语法 &lt;code>{ }&lt;/code> 来初始化集合对象。比如 &lt;code>List&lt;/code> 类型可以这样初始化：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">list&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">3&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>实际上，只要类实现了 &lt;code>IEnumerable&lt;/code> 接口，并且包含一个名为 &lt;code>Add&lt;/code> 的方法，那么这个类就可以使用集合初始化器语法。比如下面这个例子：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">PlanetCollection&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">IEnumerable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">T&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">Planets&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">init&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">_index&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">PlanetCollection&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">count&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Planets&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">T&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">count&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">T&lt;/span> &lt;span class="n">item&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">_index&lt;/span> &lt;span class="p">&amp;gt;=&lt;/span> &lt;span class="n">Planets&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">IndexOutOfRangeException&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Planets&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">_index&lt;/span>&lt;span class="p">++]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">item&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">IEnumerator&lt;/span> &lt;span class="n">GetEnumerator&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Planets&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">GetEnumerator&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>然后我们就可以这样初始化：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">collection&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">PlanetCollection&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="m">8&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;mercury&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;venus&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;earth&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;mars&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;jupiter&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;saturn&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;uranus&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;neptune&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 会被编译为：&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">PlanetCollection&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">source&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">PlanetCollection&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="m">8&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">source&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;mercury&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">source&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;venus&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">source&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;earth&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">source&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;mars&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">source&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;jupiter&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">source&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;saturn&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">source&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;uranus&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">source&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;neptune&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="元组拆分">
元组拆分
&lt;a href="#%e5%85%83%e7%bb%84%e6%8b%86%e5%88%86" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>C# 在引入了元组后，也引入了元组拆分语法。比如我们可以这样写：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">2&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">d&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="m">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">4&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>很多原生的类型也支持元组拆分。比如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">pair&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">KeyValuePair&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="s">&amp;#34;key&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">42&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">pair&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">dt&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">DateTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Now&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">year&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">month&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">day&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">dt&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>此外，如果我们声明一个 &lt;code>record&lt;/code> 类型，那么底层也会为我们提供元组拆分的功能。&lt;/p>
&lt;p>实际上，元组拆分的语法是通过 &lt;code>Deconstruct&lt;/code> 方法实现的。只要类中有一个名为 &lt;code>Deconstruct&lt;/code> 的方法，并且用 &lt;code>out&lt;/code> 的方式进行传参，那么这个类就可以使用元组拆分语法。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Point2d&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">X&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">Y&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Deconstruct&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">out&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">out&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">x&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">X&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">y&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Y&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">point&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Point2d&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">X&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Y&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">2&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">point&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="linq-的-selectmany-方法">
LINQ 的 &lt;code>SelectMany&lt;/code> 方法
&lt;a href="#linq-%e7%9a%84-selectmany-%e6%96%b9%e6%b3%95" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;p>最后再说一个比较冷门且不常用的，就是 LINQ 中的 &lt;code>SelectMany&lt;/code> 方法，以及多层 &lt;code>from&lt;/code> 语句。比如我们现在有一个“数组的数组”一样的结构，例如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Person&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Pet&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">Pets&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Pet&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Name&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我们可以使用 &lt;code>SelectMany&lt;/code> 方法来展开这个结构：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">people&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">GetPeople&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 使用查询表达式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">pets&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">from&lt;/span> &lt;span class="n">person&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">people&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">from&lt;/span> &lt;span class="n">pet&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">person&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Pets&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">select&lt;/span> &lt;span class="n">pet&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">ToList&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 或者链式表达式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">pets&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">people&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SelectMany&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Pets&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">ToList&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>实际上，只要我们为类提供正确的 &lt;code>SelectMany&lt;/code> 方法，那么我们就可以使用多层 &lt;code>from&lt;/code> 语句来展开这个结构。比如我们可以为上面&lt;a class="link" href="#%e9%9b%86%e5%90%88%e5%88%9d%e5%a7%8b%e5%8c%96%e5%99%a8" >集合初始化器&lt;/a>中的 &lt;code>PlanetCollection&lt;/code> 类型提供一个 &lt;code>SelectMany&lt;/code> 方法，从而展开这个结构：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">static&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Extensions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="n">IEnumerable&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">TResult&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">SelectMany&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">TSource&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">TCollection&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">TResult&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span> &lt;span class="n">PlanetCollection&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">TSource&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">source&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Func&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">TSource&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">IEnumerable&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">TCollection&lt;/span>&lt;span class="p">&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">collectionSelector&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Func&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">TSource&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">TCollection&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">TResult&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">resultSelector&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">source&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Planets&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">subItem&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">collectionSelector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="n">resultSelector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">subItem&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">var&lt;/span> &lt;span class="n">query&lt;/span> &lt;span class="p">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">from&lt;/span> &lt;span class="n">planet&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">collection&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">from&lt;/span> &lt;span class="n">letter&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">planet&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">select&lt;/span> &lt;span class="n">letter&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToArray&lt;/span>&lt;span class="p">()).&lt;/span>&lt;span class="n">ToUpper&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这个 &lt;code>SelectMany&lt;/code> 方法的要求相对比较复杂，这里我们就不展开讨论了。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>看了这篇文章之后，相信大家会对 C# 这门编程语言有一个更新的认识了吧？&lt;/p>
&lt;p>事实上，上面提到的大多数鸭子类型，都是与底层 C# 代码密不可分的。这里我给大家提供一个探索的方向，比如可以借助 &lt;a class="link" href="https://sharplab.io/" target="_blank" rel="noopener"
>&lt;code>SharpLab&lt;/code>&lt;/a> 这样的工具，查看诸如 &lt;code>foreach&lt;/code> 语句、&lt;code>await&lt;/code> 语句、以及集合初始化器等语法对应的底层 C# 代码。相信大家一定会有所收获。&lt;/p></description></item><item><title>EntityFrameworkCore 最小入门指南</title><link>https://blog.coldwind.top/posts/efcore-minimal-example/</link><pubDate>Wed, 13 Mar 2024 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/efcore-minimal-example/</guid><description>&lt;h2 id="安装-entityframeworkcore">
安装 EntityFrameworkCore
&lt;a href="#%e5%ae%89%e8%a3%85-entityframeworkcore" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>以 Sql Server 为例，可以在 NuGet 包管理器中搜索并安装以下包：&lt;/p>
&lt;ul>
&lt;li>&lt;code>Microsoft.EntityFrameworkCore&lt;/code>&lt;/li>
&lt;li>&lt;code>Microsoft.EntityFrameworkCore.SqlServer&lt;/code>&lt;/li>
&lt;/ul>
&lt;h2 id="实现-model">
实现 Model
&lt;a href="#%e5%ae%9e%e7%8e%b0-model" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>假定现在有这样一张表：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="cl">&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">dbo&lt;/span>&lt;span class="p">].[&lt;/span>&lt;span class="n">Blog&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">BlogId&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">INT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">IDENTITY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NOT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">Title&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">NVARCHAR&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">Author&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">NVARCHAR&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">Content&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">NVARCHAR&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">MAX&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>可以创建一个对应的 Model：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Blog&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">BlogId&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Title&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Author&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">Content&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="创建-dbcontext">
创建 DbContext
&lt;a href="#%e5%88%9b%e5%bb%ba-dbcontext" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>创建一个继承自 &lt;code>DbContext&lt;/code> 的类：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">BloggingContext&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">DbContext&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">DbSet&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="n">Blog&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">Blogs&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里其实幕后发生了一些基于 EF 命名习惯的自动配置，比如：&lt;/p>
&lt;ul>
&lt;li>&lt;code>Blog&lt;/code> 类对应的表名为其复数形式 &lt;code>Blogs&lt;/code>&lt;/li>
&lt;li>&lt;code>BlogId&lt;/code> 字段会被自动识别为主键&lt;/li>
&lt;/ul>
&lt;h2 id="配置连接字符串">
配置连接字符串
&lt;a href="#%e9%85%8d%e7%bd%ae%e8%bf%9e%e6%8e%a5%e5%ad%97%e7%ac%a6%e4%b8%b2" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>最简单的方法是直接重写 &lt;code>OnConfiguring&lt;/code> 方法：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">BloggingContext&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">DbContext&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">protected&lt;/span> &lt;span class="kd">override&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">OnConfiguring&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DbContextOptionsBuilder&lt;/span> &lt;span class="n">optionsBuilder&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">optionsBuilder&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">UseSqlServer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">@&amp;#34;Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Blogging;&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="使用-dbcontext">
使用 DbContext
&lt;a href="#%e4%bd%bf%e7%94%a8-dbcontext" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>获取 &lt;code>Blog&lt;/code> 数据：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">db&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">BloggingContext&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">blogs&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Blogs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ToList&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>添加新的 &lt;code>Blog&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">db&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">BloggingContext&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Blogs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="n">Blog&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="n">Title&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Hello World&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Author&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Alice&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Content&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Hello World!&amp;#34;&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">SaveChanges&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="总结">
总结
&lt;a href="#%e6%80%bb%e7%bb%93" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在本文中，我们通过一个最小化的示例介绍了如何使用 Entity Framework Core 进行数据访问。我们创建了一个 &lt;code>BloggingContext&lt;/code> 类来表示数据库上下文，并定义了一个 &lt;code>DbSet&amp;lt;Blog&amp;gt;&lt;/code> 来操作 &lt;code>Blog&lt;/code> 实体。我们还展示了如何配置连接字符串，以及如何使用 &lt;code>DbContext&lt;/code> 来添加和获取数据。&lt;/p>
&lt;p>这个简单的例子虽然只涉及到了基本的操作，但它为理解 EF Core 的工作原理和进一步探索其功能提供了基础。&lt;/p>
&lt;p>感谢阅读，欢迎在评论区分享你的想法和问题。&lt;/p></description></item><item><title>Benchmark.NET 简易指南</title><link>https://blog.coldwind.top/posts/benchmark-dotnet/</link><pubDate>Tue, 05 Mar 2024 10:52:37 +0800</pubDate><guid>https://blog.coldwind.top/posts/benchmark-dotnet/</guid><description>&lt;p>&lt;a class="link" href="https://benchmarkdotnet.org/" target="_blank" rel="noopener"
>Benchmark.NET&lt;/a> 是一个用于 .NET 应用程序的强大的基准测试库。它可以帮助开发人员评估他们的代码的性能，找出潜在的性能问题，并且比较不同的实现方式。Benchmark.NET 提供了丰富的特性，包括内存诊断、全局初始化、迭代初始化等，可以满足各种性能测试的需求。&lt;/p>
&lt;p>这篇文章将介绍 Benchmark.NET 的基础知识和一些常用的特性。你也可以观看&lt;a class="link" href="https://b23.tv/9rMsBmF" target="_blank" rel="noopener"
>我的 B 站教学视频&lt;/a>进行学习。&lt;/p>
&lt;h2 id="常用特性">
常用特性
&lt;a href="#%e5%b8%b8%e7%94%a8%e7%89%b9%e6%80%a7" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>一些常用的特性：&lt;/p>
&lt;ul>
&lt;li>Class
&lt;ul>
&lt;li>&lt;code>MemoryDiagnoser&lt;/code>：查看内存分配情况（有一个 bool 参数，表示是否显示 GC 的情况）&lt;/li>
&lt;li>&lt;code>SimpleJob&lt;/code>：可以设置 .NET 版本，如 &lt;code>RuntimeMoniker.Net60&lt;/code>&lt;/li>
&lt;li>&lt;code>Orderer(SummaryOrderPolicy.SlowestToFastest)&lt;/code>：输出结果的排序&lt;/li>
&lt;li>&lt;code>RankColumn&lt;/code>：为结果表格添加一列 Rank，表示当前行的方法的排名&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Method
&lt;ul>
&lt;li>&lt;code>Benchmark&lt;/code>：表示这个方法需要被测试（另有一个 &lt;code>Baseline&lt;/code> 参数，同时会给结果添加一列 Ratio，表示和 Baseline 的比率）&lt;/li>
&lt;li>&lt;code>Arguments&lt;/code>：类似于 &lt;code>Params&lt;/code>，表示该方法的传参，可以有多个，并且会和 &lt;code>Params&lt;/code> 联动，充分考虑各种组合&lt;/li>
&lt;li>&lt;code>GlobalSetup&lt;/code>：全局初始化，常用于初始化一个要用来测试的变量、集合等。可以和 &lt;code>Params&lt;/code> 联动，比如数组的容量由某个字段决定&lt;/li>
&lt;li>&lt;code>IterationSetup&lt;/code>：用于在每次迭代前的初始化，每次迭代都会调用一次&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Field
&lt;ul>
&lt;li>&lt;code>Params&lt;/code>：某个字段可能有不同的值（如果多个字段被标记该特性，则会充分考虑所有参数的组合）&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="实际例子">
实际例子
&lt;a href="#%e5%ae%9e%e9%99%85%e4%be%8b%e5%ad%90" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;h3 id="测试排序的效率">
测试排序的效率
&lt;a href="#%e6%b5%8b%e8%af%95%e6%8e%92%e5%ba%8f%e7%9a%84%e6%95%88%e7%8e%87" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-C#" data-lang="C#">&lt;span class="line">&lt;span class="cl">&lt;span class="na">[MemoryDiagnoser]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">SortTester&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">testList&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [GlobalSetup]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Setup&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">testList&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Enumerable&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">100&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">Shuffle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="n">Random&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1334&lt;/span>&lt;span class="p">)).&lt;/span>&lt;span class="n">ToList&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [Benchmark]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">ListSort&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">lst&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="n">testList&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">lst&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Sort&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">lst&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [Benchmark]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">LinqOrder&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">testList&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Order&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">ToList&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="测试初始化数组的效率">
测试初始化数组的效率
&lt;a href="#%e6%b5%8b%e8%af%95%e5%88%9d%e5%a7%8b%e5%8c%96%e6%95%b0%e7%bb%84%e7%9a%84%e6%95%88%e7%8e%87" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-C#" data-lang="C#">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">ListInit&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [Params(16, 128, 1060)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">count&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [Benchmark(Baseline = true)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">WithoutInit&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">count&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">res&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [Benchmark]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">WithInit&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;(&lt;/span>&lt;span class="n">count&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="m">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="n">count&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">++)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">res&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">res&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [Benchmark]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">WithLinq&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">Enumerable&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">count&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">ToList&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="net-6-vs-net-7">
.NET 6 vs. .NET 7
&lt;a href="#net-6-vs-net-7" class="anchor">&amp;para;&lt;/a>
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-C#" data-lang="C#">&lt;span class="line">&lt;span class="cl">&lt;span class="na">[MemoryDiagnoser(false)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[SimpleJob(RuntimeMoniker.Net60)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">[SimpleJob(RuntimeMoniker.Net70)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">SortTester&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="n">IEnumerable&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">testList&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [GlobalSetup]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Setup&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">testList&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Enumerable&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">10&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">ToArray&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [Benchmark]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">CalcMin&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">testList&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Min&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="注意事项">
注意事项
&lt;a href="#%e6%b3%a8%e6%84%8f%e4%ba%8b%e9%a1%b9" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;ol>
&lt;li>要使用有编译器优化的 Release 模式&lt;/li>
&lt;li>被测试的类、使用了特性的方法与字段均需要为 &lt;code>public&lt;/code>&lt;/li>
&lt;li>在要测试的方法中尽量避免会被 JIT 优化掉的情况，比如有一个不会被使用的变量等&lt;/li>
&lt;li>除非还想要测试内存读取的速度等，否则一般没有必要创建过大的数组&lt;/li>
&lt;/ol>
&lt;h2 id="参考链接">
参考链接
&lt;a href="#%e5%8f%82%e8%80%83%e9%93%be%e6%8e%a5" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>&lt;a class="link" href="https://fransbouma.github.io/BenchmarkDotNet/RulesOfBenchmarking.htm" target="_blank" rel="noopener"
>Rules of benchmarking - BenchmarkDotNet Documentation&lt;/a>&lt;/p></description></item><item><title>C# 代码格式化工具 CSharpier 上手指南</title><link>https://blog.coldwind.top/posts/csharpier/</link><pubDate>Mon, 16 Oct 2023 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/csharpier/</guid><description>&lt;img src="https://s2.loli.net/2024/04/14/qZeQzEnSDvWpPyL.png" alt="Featured image of post C# 代码格式化工具 CSharpier 上手指南" />&lt;h2 id="简介">
简介
&lt;a href="#%e7%ae%80%e4%bb%8b" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>&lt;a class="link" href="https://csharpier.com/" target="_blank" rel="noopener"
>CSharpier&lt;/a> 是一个针对 C# 代码的格式化工具，它可以帮助开发者自动化地调整代码的格式，使其更加一致和易于阅读。CSharpier 提供了丰富的配置选项，可以根据项目的需求定制代码格式化的规则。&lt;/p>
&lt;p>它的官方介绍是「CSharpier is an opinionated code formatter for C#」，其中的“opinionated”是一个英文词，意思是“有主见的”或“有偏见的”。它想表达的是，该工具对代码格式化有自己的偏好和主见，即它会按照自己的规则来格式化代码，而不是完全按照用户的意愿。在下面的内容中，大家不难看出，CSharpier 几乎没有提供多少可以配置的选项。&lt;/p>
&lt;h2 id="安装">
安装
&lt;a href="#%e5%ae%89%e8%a3%85" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>在 VS 的扩展中安装了 CSharpier 后，重启 VS 后会在上方提示安装工具，但是也可以自行安装，方式如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">dotnet tool install -g csharpier
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>如果希望更新，那么可以：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">dotnet tool update -g csharpier
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>除了 VS，VS Code、Rider 中也都有同名的扩展。&lt;/p>
&lt;h2 id="配置">
配置
&lt;a href="#%e9%85%8d%e7%bd%ae" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>可以在项目的根目录（通常与 &lt;code>.sln&lt;/code> 文件位置相同）创建一个配置文件，可以是下面三个的任意一种：&lt;/p>
&lt;ul>
&lt;li>&lt;code>.csharpierrc&lt;/code>&lt;/li>
&lt;li>&lt;code>.csharpierrc.json&lt;/code>&lt;/li>
&lt;li>&lt;code>.csharpierrc.yaml&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>支持的配置项非常少，常用的一些如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;printWidth&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">100&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;useTabs&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;tabWidth&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;preprocessorSymbolSets&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;DEBUG&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;DEBUG,CODE_STYLE&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>或者&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">printWidth&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">100&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">useTabs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">tabWidth&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">preprocessorSymbolSets&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;DEBUG&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;DEBUG,CODE_STYLE&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>其中最后一个配置项与代码中预编译器指令（如 &lt;code>#if DEBUG&lt;/code>）有关，详见&lt;a class="link" href="https://csharpier.com/docs/Configuration" target="_blank" rel="noopener"
>官方的配置文档&lt;/a>。&lt;/p>
&lt;h2 id="实用场景">
实用场景
&lt;a href="#%e5%ae%9e%e7%94%a8%e5%9c%ba%e6%99%af" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>这里我随便写了一大段 C# 代码，大家可以拷贝到自己常用的 C# 开发工具中，然后使用 CSharpier 格式化，从而查看效果。&lt;/p>
&lt;p>CSharpier 还提供了一个&lt;a class="link" href="https://playground.csharpier.com/" target="_blank" rel="noopener"
>Playground&lt;/a>，方便大家在线体验它的效果。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CSharpierDemo&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="n">allowedExtensions&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="s">&amp;#34;.jpg&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;.jpeg&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;.png&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;.gif&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;.bmp&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;.tiff&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;.tif&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;.webp&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;.heic&amp;#34;&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">[,]&lt;/span> &lt;span class="n">map&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="p">[,]&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="m">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">3&lt;/span> &lt;span class="p">},&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="m">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">6&lt;/span> &lt;span class="p">},&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="m">7&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">8&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">9&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="k">readonly&lt;/span> &lt;span class="n">DependencyProperty&lt;/span> &lt;span class="n">MyPropertyProperty&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">DependencyProperty&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Register&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;MyProperty&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">typeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="k">typeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MyControl&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">PropertyMetadata&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [JsonIgnore]&lt;/span> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">MyProperty1&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [JsonIgnore]&lt;/span> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">MyProperty2&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na"> [JsonIgnore]&lt;/span> &lt;span class="kd">public&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">MyProperty3&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">get&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="k">set&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">Foo&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">allowedExtensions&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Select&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Trim&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">ToLower&lt;/span>&lt;span class="p">()).&lt;/span>&lt;span class="n">Select&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">TrimLeft&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sc">&amp;#39;.&amp;#39;&lt;/span>&lt;span class="p">)).&lt;/span>&lt;span class="n">Where&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="m">3&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">ToList&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">ForEach&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="n">Console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">WriteLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">exts&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">from&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">allowedExtensions&lt;/span> &lt;span class="k">select&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Trim&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">ToLower&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">into&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="k">select&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">TrimLeft&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sc">&amp;#39;.&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">into&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="k">where&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Length&lt;/span> &lt;span class="p">==&lt;/span> &lt;span class="m">3&lt;/span> &lt;span class="k">select&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">private&lt;/span> &lt;span class="k">void&lt;/span> &lt;span class="n">FooWithManyParameters&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="n">FromHeader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;Id&amp;#34;&lt;/span>&lt;span class="p">)]&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">FromQuery&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;first_name&amp;#34;&lt;/span>&lt;span class="p">)]&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">firstName&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">FromQuery&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;last_name&amp;#34;&lt;/span>&lt;span class="p">)]&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="n">lastName&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">string?&lt;/span> &lt;span class="n">middleName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Action&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">callback&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Action&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">errorCallback&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div></description></item><item><title>可以用盗版，但是要怀着愧疚的心</title><link>https://blog.coldwind.top/posts/use-pirate-with-guilty/</link><pubDate>Sun, 25 Dec 2022 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/posts/use-pirate-with-guilty/</guid><description>&lt;img src="https://s2.loli.net/2024/04/20/9dsCD7m1FJBQKZz.png" alt="Featured image of post 可以用盗版，但是要怀着愧疚的心" />&lt;p>.NET 开发这一领域有一款特别有名的小工具名叫 LINQPad，是一位国外技术大牛开发的软件，能够显著提高 .NET 工程师的效率，但是价格也一点不便宜。除了（基本上是个半残的）免费版，还有专业版、开发者版以及尊享版，而我买的就是最贵的版本，$115，按照当时的汇率，足足 800+ RMB。但是我认为买得很值。我一直在用，它帮上了大忙，那么我觉得它是完全值得我花这笔钱的。&lt;/p>
&lt;p>不仅如此，我还觉得这款软件在国外非常火爆，在国内却鲜有人知道，所以我专门制作了一期视频分享给了大家。&lt;/p>
&lt;div class="video-wrapper">
&lt;iframe src="https://player.bilibili.com/player.html?as_wide=1&amp;amp;high_quality=1&amp;amp;page=1&amp;bvid=BV1544y1S7j3&amp;mute=0&amp;autoplay=0"
scrolling="no"
frameborder="no"
framespacing="0"
allowfullscreen="true"
>
&lt;/iframe>
&lt;/div>
&lt;p>不久之后，我就看到了评论区这么一条内容：&lt;/p>
&lt;p>&lt;img src="https://s2.loli.net/2024/04/20/EwK7Upm6tWbOCHZ.png"
loading="lazy"
alt="软件是好软件，可惜太贵啦"
>&lt;/p>
&lt;p>这条评论我觉得真是又好气又好笑。这句话实在是不合逻辑，槽点太多，也让我看到了一些人对于所谓的付费软件以及程序员群体有着怎样的误解。&lt;/p>
&lt;h2 id="只适合专业程序员">
只适合专业程序员？
&lt;a href="#%e5%8f%aa%e9%80%82%e5%90%88%e4%b8%93%e4%b8%9a%e7%a8%8b%e5%ba%8f%e5%91%98" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>首先第一条，什么叫“可惜太贵，只适合专业程序员”？意思是说，不专业的程序员，或者学生党之类的就不适合？这款软件虽然最高一档的价值 800+，但是免费版也凑活着能用啊？或者你咬咬牙，花个不到 400 元就可以买一个专业版了。讲道理，400 块算多吗？放在氪金手游里面，怕不是只能抽出来一些卡池里面的乐色吧？我现在上班，每天光是坐地铁就要 10 元，一个月就是 200 多；每天三餐要花三四十，一个月就要 1000 块；这年头手机的话费，一年怎么着不得 400+？为什么这些都像是习以为常的东西，眼前的一款买断制的软件你却下不了手了？&lt;/p>
&lt;p>更重要的是，买了这款软件，它可以帮助你不说一辈子了，最起码在接下来几年的学习工作生涯的黄金期里助你一臂之力，大幅提高工作学习的效率，明显发挥出软件自身的价值，难道不比你买一个氪个什么 328、648 要有意义得多？&lt;/p>
&lt;h2 id="程序员不在乎这点钱">
程序员不在乎这点钱？
&lt;a href="#%e7%a8%8b%e5%ba%8f%e5%91%98%e4%b8%8d%e5%9c%a8%e4%b9%8e%e8%bf%99%e7%82%b9%e9%92%b1" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>第二条，什么叫“程序员不在乎这点钱”？意思是说程序员都是高薪行业的人才，一个个月入好几万，根本不会在乎这么一丁点钱？不好意思，你又错了。首先，程序员就不是你想象中的这么高薪。或许能月入个一万多，但是大都是在北上广深这些地方，每月光是房租就要花个好几千，最后算下来也不剩多少了。就这还是拿身体换来的，一个个年纪轻轻地就眼睛也不好了，颈椎腰椎也不好了，手腕也不好了，头发也不剩多少了。所以程序员也不见得就不在乎这点钱的。&lt;/p>
&lt;p>这种说的还是单身的没有梦想成为咸鱼的程序员。如果想结婚生子，想买房买车，想提升自己，给自己买个网课、健身房会员卡什么的，那更是开支大了去了。你月入 40K 又如何？说不定月供就两三万。所以专业程序员就不差钱了？真是想太多了。&lt;/p>
&lt;h2 id="不差钱的程序员就会愿意掏钱">
不差钱的程序员就会愿意掏钱？
&lt;a href="#%e4%b8%8d%e5%b7%ae%e9%92%b1%e7%9a%84%e7%a8%8b%e5%ba%8f%e5%91%98%e5%b0%b1%e4%bc%9a%e6%84%bf%e6%84%8f%e6%8e%8f%e9%92%b1" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>再然后，“他们不差这点钱”，所以他们就“适合买这款软件了”？不好意思，我身边“在乎这点钱”的程序员多了去了。我认识一位程序员，每周工作日至少喝三四杯国内某知名连锁店铺的奶茶，每杯按 18 块算，一个月最少也要 200 多块了。但是咧，人家连 Office 用的都是盗版的 2010，整天标题栏都是大红色的，连个京东淘宝上搞活动 200 多块的正版 Office 365（后来改名叫 Microsoft 365 了）都舍不得买，即便人家每天到手的工资都够买原价的 365 了。这种人有可能会愿意花好几百买一款没有任何开发功能，只是方便自己工作学习的小软件？&lt;/p>
&lt;p>我身边这样的程序员真的是不少。我推荐他们用 XMind，明明这玩意有免费的试用版，但是他们就是喜欢一上去就在百度搜索破解版，最后下了个盗版的 XMind 8；开发软件用的 IDE，直接去搜个破解版 JetBrains 系列的，都不知道有免费的 Visual Studio 社区版以及 Visual Studio Code 可以用的；iPad 上看 PDF 记个笔记或者画个画，连个 Notability 或者 Procreate 都舍不得买，非得去找个多人共用的苹果账号用“分享版”的，不知道哪天 APP 就不能更新了。那我要是推荐他们去用 LINQPad，不用说，肯定去找盗版了，明明还有一个免费的平替 RoslynPad。&lt;/p>
&lt;p>所以我想说的是，并不是所有程序员都不觉得这笔钱不是钱，也并不是所有程序员在买得起这款软件的情况下就会愿意掏钱。退一步说，就算是舍不得买正版，也不代表你就要去用盗版啊？我买不起正版 PS，但我买得起 Affinity Photo；我买不起 PR，但我可以用免费的 DaVinci；买不起 Office，但还可以用 WPS（如果你能接受广告什么的）或者 LibreOffice；买不起 LINQPad，那还可以用平替的 RoslynPad，或者不然就在 VS 里面新建一个 Console App 呗。&lt;/p>
&lt;h2 id="到底什么样的人适合这款软件">
到底什么样的人适合这款软件？
&lt;a href="#%e5%88%b0%e5%ba%95%e4%bb%80%e4%b9%88%e6%a0%b7%e7%9a%84%e4%ba%ba%e9%80%82%e5%90%88%e8%bf%99%e6%ac%be%e8%bd%af%e4%bb%b6" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>那到底什么样的人才适合这款软件呢？我开头就已经说了，这款软件可以帮到这个行业的所有人群，不论你的技术是强还是弱，你的钱包是鼓还是瘪。只要你有着一颗想要提升自己的心，以及对正版优质软件的认可，那么我都认为你可以去买这款软件。&lt;/p>
&lt;p>而这样的人恐怕并不是很多。有的图像工作者，已经依靠修图挣了很多钱，却不愿买正版的 PS；有的音乐工作者，依靠编曲已经足够养家糊口，但依旧不愿意买个正版 FL；有的程序员，已经依靠写代码挣了这么多钱，却依旧不愿意为他们使用的软件付个费，凡事总是想着去搜索破解版，甚至不以为耻，反而觉得占到了多大的便宜。当然还有些蠢货觉得这都是外国软件，跟境外势力有什么好客气的，或者“慈禧太后当年都把钱给掏过了”之类，那我真是无 f*ck 说。&lt;/p>
&lt;p>曾经有一个同事得知我用的是每年花费好几百的正版 Office 订阅，觉得不以为然，说自己用的是破解版，不照样可以用吗，还省下了不少钱。我没有回答他，但是我有一句差点到嘴边了的话被我给咽了回去：请问你每年比我省下了好几百，那么你比我多得到了什么呢？&lt;/p>
&lt;h2 id="到底能不能用盗版软件">
到底能不能用盗版软件？ 
&lt;a href="#%e5%88%b0%e5%ba%95%e8%83%bd%e4%b8%8d%e8%83%bd%e7%94%a8%e7%9b%97%e7%89%88%e8%bd%af%e4%bb%b6" class="anchor">&amp;para;&lt;/a>
&lt;/h2>&lt;p>最后，我想聊的是，盗版软件到底能不能用？我认为可以用，但是要时刻知道这样做是不对的。Minecraft《我的世界》（以下简称 MC）是一款全球知名的沙盒类游戏，但是正版是要钱的。有一位学生写信给 MC 的创始人 Notch 说，自己想玩 MC，但是没有足够的钱，问能不能玩盗版的？Notch 笑着回答说，可以玩，但是要怀着愧疚的心。&lt;/p>
&lt;p>&lt;img src="https://s2.loli.net/2024/04/20/9dsCD7m1FJBQKZz.png"
loading="lazy"
alt="dont forget to feel bad. ;)"
>&lt;/p>
&lt;p>你如果对这款软件实际的效果不够放心，那你大可以去找一个破解版，然后用上一段时间（虽然它本身也提供了 30 天无条件退款）。如果你觉得这个软件不行，那你可以不去用它；如果你觉得这款软件不错，那你可以选择付费；但是你不能就这么一直用着盗版。用一段时间的盗版之后，如果觉得软件确实很好，那么我相信，善良正直的你，会选择为这款优秀的软件补票的。&lt;/p>
&lt;p>&lt;img src="https://s2.loli.net/2024/04/20/oPbWSFTZ4DMquBI.png"
loading="lazy"
alt="评论区的“已下到破解版”"
>&lt;/p>
&lt;p>这也正是我对那些使用破解版软件的人的建议。&lt;/p></description></item><item><title>归档</title><link>https://blog.coldwind.top/archives/</link><pubDate>Sun, 06 Mar 2022 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/archives/</guid><description/></item><item><title>关于我</title><link>https://blog.coldwind.top/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/about/</guid><description>&lt;p>我是一名 .NET 软件开发工程师，喜欢学习及分享编程技术。你可以在 B 站找到我：&lt;a class="link" href="https://space.bilibili.com/600592" target="_blank" rel="noopener"
>@十月的寒流&lt;/a>。&lt;/p>
&lt;p>我现在在学习的技术有：&lt;/p>
&lt;ul>
&lt;li>Avalonia UI&lt;/li>
&lt;li>Blazor&lt;/li>
&lt;li>PostgreSQL&lt;/li>
&lt;li>Go&lt;/li>
&lt;li>……&lt;/li>
&lt;/ul>
&lt;p>平时的我喜欢学习语言（English、日本語），吹吹半音阶口琴，玩一些单机游戏，偶尔可能还会发点&lt;a href="https://yunwuyue.xyz" target="_blank">牢骚&lt;/a>。学习语言之余，有时我还会翻译一些外语内容，和大家分享一些有趣的东西。&lt;/p>
&lt;p>如果你想联系我，可以发送邮件到：&lt;a class="link" href="mailto:albedo.shade@proton.me" >albedo.shade@proton.me&lt;/a>&lt;/p>
&lt;p>当然，你也可以在本博客任意评论区留言，或者在 B 站视频评论区留言以及私信我。&lt;/p></description></item><item><title>搜索</title><link>https://blog.coldwind.top/search/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://blog.coldwind.top/search/</guid><description/></item></channel></rss>