[译] WinForms:分析一下(我用 Visual Basic 写的)

news/2025/1/30 22:07:55/文章来源:https://www.cnblogs.com/MingsonZheng/p/18692763

原文 | Klaus Loeffelmann

翻译 | 郑子铭

如果您从未看过电影《分析这一点》,下面是简短的介绍:假设一个纽约家族的成员有可疑的习惯,他决定认真考虑接受治疗以改善他的精神状态。在比利·克里斯托和罗伯特·德尼罗的推动下,剧情一定会很有趣。虽然《分析这一点!》讽刺性地处理了被漫画化的 MOB 世界的问题,但使用正确的分析工具找到问题的根源在任何地方都至关重要。在任务关键型 LOB-App 世界中更是如此。

进入新的 WinForms Roslyn Analyzers,这是 WinForms 应用程序的特定领域“顾问”。借助 .NET 9,我们推出了这些分析器,以帮助您的代码解决其潜在问题 — 无论是错误行为、可疑模式还是改进机会。

Roslyn 分析器到底是什么?

Roslyn 分析器是 Roslyn 编译器平台的核心部分,可在后台无缝工作,在您编写代码时对其进行分析。您可能已经使用它们多年却没有意识到这一点。Visual Studio 中的许多功能(如代码修复、重构建议和错误诊断)都依赖于分析器或 CodeFixes,甚至只是它们本身,以增强您的开发过程。它们已成为现代开发不可或缺的一部分,以至于我们常常将它们视为理所当然,只是“事物的工作原理”。

最酷的是:这个基于 Roslyn 的编译器平台不是黑盒子。它们提供了极其丰富的 API,不仅 Microsoft 的 Visual Studio IDE 或编译器团队可以创建分析器。每个人都可以。这就是 WinForms 选择这项技术来改善 WinForms 编码体验的原因。

这只是开始 — 未来还有更多

借助 .NET 9,我们为 WinForms 专用分析器奠定了基础架构,并引入了第一组规则。这些分析器旨在解决安全性、稳定性和生产力等关键领域。虽然这只是开始,但我们致力于在未来版本中扩大其范围,并推出更多规则和功能。

那么,让我们真正了解一下我们为 .NET 9 引入的第一组分析器:

选择正确的 InvokeAsync 重载的指南

在 .NET 9 中,我们为 WinForms 引入了一系列新的异步 API。这篇博文详细介绍了新的 WinForms 异步功能。这是我们认为 WinForms Analyzer 可以在防止异步代码出现问题方面提供很大帮助的首批领域之一。

使用新的 Control.InvokeAsync API 时面临的一个挑战是从以下选项中选择正确的重载:

public async Task InvokeAsync(Action callback, CancellationToken cancellationToken = default)
public async Task<T> InvokeAsync<T>(Func<T> callback, CancellationToken cancellationToken = default)
public async Task InvokeAsync(Func<CancellationToken, ValueTask> callback, CancellationToken cancellationToken = default)
public async Task<T> InvokeAsync<T>(Func<CancellationToken, ValueTask<T>> callback, CancellationToken cancellationToken = default)

每个重载都支持同步和异步方法的不同组合,有或没有返回值。链接的博客文章提供了有关这些 API 的全面背景信息。

但是,选择错误的重载可能会导致应用程序中的代码路径不稳定。为了缓解这种情况,我们实施了一个分析器,以帮助开发人员根据其特定用例选择最合适的 InvokeAsync 重载。

潜在的问题如下:InvokeAsync 可以异步调用同步和异步方法。对于异步方法,您可能会传递 Func<Task>,并期望它被等待,但事实并非如此。Func<T> 专门用于异步调用同步调用方法 - 其中 Func<Task> 只是一个不幸的特殊情况。

换句话说,问题出现是因为 InvokeAsync 可以接受任何 Func<T>。但只有 Func<CancellationToken, ValueTask> 才能被 API 正确等待。如果您传递没有正确签名的 Func<Task>(不接受 CancellationToken 并返回 ValueTask 的签名),它将不会被等待。这会导致“发射后不管”的情况,其中函数内的异常无法正确处理。如果这样的函数随后抛出异常,它可能会损坏数据,甚至导致整个应用程序崩溃。

看看以下场景:

private async void StartButtonClick(object sender, EventArgs e)
{_btnStartStopWatch.Text = _btnStartStopWatch.Text != "Stop" ? "Stop" : "Start";await Task.Run(async () =>{while (true){await this.InvokeAsync(UpdateUiAsync);}});// ****// The actual UI update method// ****async Task UpdateUiAsync(){_lblStopWatch.Text = $"{DateTime.Now:HH:mm:ss - fff}";await Task.Delay(20);}
}

这是一个典型的场景,InvokeAsync 的重载被意外使用,而它本应返回任务以外的内容。但分析器指出:

因此,通过此通知,我们也清楚地认识到我们实际上需要引入一个取消令牌,以便我们可以正常结束正在运行的任务,无论是当用户再次单击按钮时,还是当 Form 实际关闭时(这更重要)。否则,任务将继续运行,而 Form 将被处置。这将导致崩溃:

    private async void ButtonClick(object sender, EventArgs e){if (_stopWatchToken.CanBeCanceled){_btnStartStopWatch.Text = "Start";_stopWatchTokenSource.Cancel();_stopWatchTokenSource.Dispose();_stopWatchTokenSource = new CancellationTokenSource();_stopWatchToken = CancellationToken.None;return;}_stopWatchToken = _stopWatchTokenSource.Token;_btnStartStopWatch.Text = "Stop";await Task.Run(async () =>{while (true){try{await this.InvokeAsync(UpdateUiAsync, _stopWatchToken);}catch (TaskCanceledException){break;}}});// ****// The actual UI update method// ****async ValueTask UpdateUiAsync(CancellationToken cancellation){_lblStopWatch.Text = $"{DateTime.Now:HH:mm:ss - fff}";await Task.Delay(20, cancellation);}}protected override void OnFormClosing(FormClosingEventArgs e){base.OnFormClosing(e);_stopWatchTokenSource.Cancel();}

分析器通过检测 InvokeAsync 的不兼容用法并指导您选择正确的重载来解决此问题。这可确保异步代码中行为稳定、可预测且异常处理正确。

防止设计时业务数据泄露

在开发自定义控件或从 UserControl 派生的业务控制逻辑类时,通常使用属性来管理其行为和外观。然而,如果在设计时无意中设置了这些属性,就会出现一个常见问题。这通常是因为在设计阶段没有适当的机制来防范此类情况。

如果未正确配置这些属性以控制其代码序列化行为,则设计时设置的敏感数据可能会无意中泄漏到生成的代码中。此类泄漏可能导致:

  • 敏感数据暴露在源代码中,可能发布在 GitHub 等平台上。
  • 设计时数据嵌入到资源文件中,要么是因为缺少相关属性类型的必要 TypeConverters,要么是因为表单已本地化。

这两种情况都会对应用程序的完整性和安全性造成重大风险。

此外,我们力求尽可能避免资源序列化。在 .NET 9 中,由于安全性和可维护性问题,二进制格式化程序和相关 API 已被淘汰。这使得仔细控制哪些数据被序列化以及如何序列化变得更加重要。

二进制格式化程序过去用于序列化对象,但它存在许多安全漏洞,使其不适合现代应用程序。在 .NET 9 中,我们完全消除了这个序列化程序,以减少攻击面并提高应用程序的可靠性。任何对资源序列化的依赖都有可能再次引入这些风险,因此采用更安全的做法至关重要。

为了帮助您(开发人员)解决这个问题,我们引入了一个 WinForms 特定的分析器。当缺少以下所有用于控制属性的 CodeDOM 序列化过程的机制时,此分析器将激活:

  1. SerializationVisibilityAttribute:此属性控制 CodeDOM 序列化程序应如何(或是否)序列化属性的内容。
  2. 属性不是只读的,因为 CodeDOM 序列化程序默认忽略只读属性。
  3. DefaultValueAttribute:此属性定义属性的默认值。如果应用,CodeDOM 序列化程序仅在设计时的当前值与默认值不同时序列化属性。
  4. 未实现相应的 private bool ShouldSerialize<PropertyName>() 方法。在设计(序列化)时调用此方法来确定是否应序列化属性的内容。

通过确保至少存在其中一种机制,您可以避免意外的序列化行为,并确保在设计时 CodeDOM 序列化过程中正确处理您的属性。

但是……这个分析器破坏了我的整个解决方案!

假设您在 .NET 8 中开发了一个特定于域的 UserControl,如上面的屏幕截图所示。现在,您正在将项目重新定位到 .NET 9。那么,显然,在那一刻,分析器启动了,您可能会看到类似这样的内容:

与之前讨论的 Async Analyzer 不同,此分析器附带了 Roslyn CodeFix。如果您想通过指示 CodeDOM 序列化程序无条件地永不序列化属性内容来解决这个问题,您可以使用 CodeFix 进行必要的更改:

如您所见,您甚至可以在整个文档中一次性修复它们。在大多数情况下,这已经是正确的做法:分析器在每个标记属性的顶部添加 SerializationVisibilityAttribute,确保它不会被无意中序列化,这正是我们想要的:

   ...[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]public string NameText{get => textBoxName.Text;set => textBoxName.Text = value;}[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]public string EmailText{get => textBoxEmail.Text;set => textBoxEmail.Text = value;}[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]public string PhoneText{get => textBoxPhone.Text;set => textBoxPhone.Text = value;}...

Copilot 来救场了!

还有一种更有效的方法来处理属性的必要编辑修改。您可能想问自己的问题是:如果根本没有应用任何属性来控制属性的某些方面,那么不仅要确保正确的序列化指导,还要应用其他设计时属性是否有意义?

但话又说回来,所需的努力会更大吗?

那么,如果我们利用 Copilot 来修改所有在设计时真正有用的相关属性,如 DescriptionAttributeCategoryAttribute,会怎么样?让我们试一试,如下所示:

根据您为 Copilot 选择的语言模型,您应该看到这样的结果:我们不仅解决了分析器指出的问题,而且 Copilot 还负责添加在上下文中有意义的其余属性。

Copilot 向您显示它想要添加的代码,您只需单击一下鼠标即可合并建议的更改。

而且这些问题肯定不是 Copilot 能够帮助您实现现有 WinForms 应用程序现代化的唯一领域。

但是,如果分析器在整个解决方案中标记了数百个问题,请不要惊慌!还有更多选项可以在代码文件、项目甚至解决方案级别配置分析器的严重性:

根据范围抑制分析器

首先,您可以选择抑制不同范围内的分析器:

  • 在源代码中:此选项在标记代码周围的源文件中直接插入 #pragma warning disable 指令。此方法适用于本地化、一次性抑制,其中分析器警告是不必要的或无关紧要的。例如:
#pragma warning disable WFO1000
public string SomeProperty { get; set; }
#pragma warning restore WFO1000
  • 在抑制文件中:这会将抑制添加到项目中名为 GlobalSuppressions.cs 的文件中。此文件中的抑制作用范围是全局的,范围是程序集或命名空间,因此对于较大规模的抑制来说,这是一个不错的选择。例如:
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("WinForms.Analyzers","WFO1000",Justification = "This property is intentionally serialized.")]
  • 通过属性在源代码中:这会将抑制属性直接应用于特定代码元素,例如类或属性。如果您希望抑制仍是源代码文档的一部分,这是一个不错的选择。例如:
[System.Diagnostics.CodeAnalysis.SuppressMessage("WinForms.Analyzers","WFO1000",Justification = "This property is handled manually.")]
public string SomeProperty { get; set; }

在 .editorconfig 中配置分析器严重性

要为您的项目或解决方案集中配置分析器严重性,您可以使用 .editorconfig 文件。此文件允许您为特定分析器定义规则,包括其严重性级别,例如无、建议、警告或错误。例如,要更改 WFO1000 分析器的严重性:

# Configure the severity for the WFO1000 analyzer
dotnet_diagnostic.WFO1000.severity = warning

使用 .editorconfig 文件进行目录特定设置

.editorconfig 文件的强大功能之一是它们能够控制解决方案不同部分的设置。通过将 .editorconfig 文件放置在解决方案内的不同目录中,您可以将设置仅应用于特定项目、文件夹或文件。配置按层次结构应用,这意味着子目录的 .editorconfig 文件中的设置可以覆盖父目录中的设置。

例如:

  • 根级 .editorconfig:将通用 .editorconfig 文件放置在解决方案根目录中,以定义应用于整个解决方案的默认设置。
  • 项目特定 .editorconfig:将另一个 .editorconfig 文件放置在特定项目的目录中,以便在从根目录继承设置的同时为该项目应用不同的规则。
  • 文件夹特定 .editorconfig:如果某些文件夹(例如,测试项目、遗留代码)需要唯一设置,您可以向这些文件夹添加 .editorconfig 文件以覆盖继承的配置。
/solution-root├── .editorconfig (applies to all projects)├── ProjectA/│    ├── .editorconfig (overrides root settings for ProjectA)│    └── CodeFile.cs├── ProjectB/│    ├── .editorconfig (specific to ProjectB)│    └── CodeFile.cs├── Shared/│    ├── .editorconfig (applies to shared utilities)│    └── Utility.cs

在此布局中,根目录中的 .editorconfig 将常规设置应用于解决方案中的所有文件。ProjectA 中的 .editorconfig 将应用特定于 ProjectA 的附加或覆盖规则。同样,ProjectB 和共享目录可以定义其唯一设置。

  • 目录特定 .editorconfig 文件的用例测试项目:禁用或降低某些测试项目的某些分析器的严重性,因为某些规则可能不适用。
# In TestProject/.editorconfig
dotnet_diagnostic.WFO1000.severity = none
Legacy Code: Suppress analyzers entirely or reduce their impact for legacy codebases to avoid unnecessary noise.
# In LegacyCode/.editorconfig
dotnet_diagnostic.WFO1000.severity = suggestion
Experimental Features: Use more lenient settings for projects under active development while enforcing stricter rules for production-ready code.

通过策略性地放置 .editorconfig 文件,您可以对分析器的行为和编码约定进行细粒度控制,从而更轻松地管理具有各种要求的大型解决方案。请记住,此分析器的目标是引导您编写更安全、更易于维护的代码,但由您决定在项目中解决这些问题的最佳速度和优先级。

如您所见:.editorconfig 文件或一组经过深思熟虑的此类文件提供了一种集中且一致的方式来管理整个项目或团队的分析器行为。

有关更多详细信息,请参阅 .editorconfig 文档。

所以,我对 WinForms 分析器有很好的想法 - 我可以贡献吗?

当然!WinForms 团队和社区一直在寻找改善开发人员体验的想法。如果您对新分析器或现有分析器的增强功能有建议,您可以通过以下方式做出贡献:

  1. 打开问题:前往 WinForms GitHub 存储库并打开一个问题来描述您的想法。尽可能详细地解释您的分析器将解决的问题及其工作原理。
  2. 加入讨论:在 GitHub 或其他论坛上与 WinForms 社区互动。其他开发人员的反馈可以帮助您完善您的想法。
  3. 贡献代码:如果您熟悉 .NET Roslyn 分析器框架,请考虑实施您的想法并向存储库提交拉取请求。团队积极审查和合并社区贡献。
  4. 测试和迭代:在提交拉取请求之前,请使用真实场景彻底测试您的分析器,以确保它按预期工作并且不会引入误报。
    为生态系统做出贡献不仅可以帮助他人,还可以加深您对 WinForms 开发和 .NET 平台的理解。

最后的话

分析器是帮助开发人员编写更好、更可靠和安全的代码的强大工具。虽然它们最初看起来可能具有侵入性——尤其是当它们标记许多问题时——但它们可以充当安全网,指导您避免常见的陷阱并采用最佳实践。

新的 WinForms 专用分析器是我们持续努力的一部分,旨在使平台现代化和保护,同时保持其简单性和易用性。无论您是在处理旧应用程序还是构建新应用程序,这些工具都旨在让您的开发体验更加顺畅。

如果您遇到问题或有改进想法,我们很乐意听取您的意见!WinForms 因其热情而专注的社区而蓬勃发展了数十年,您的贡献确保它继续发展并在当今的发展格局中保持相关性。

祝您编码愉快!

原文链接

WinForms: Analyze This (Me in Visual Basic)

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

如有任何疑问,请与我联系 (MingsonZheng@outlook.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/876381.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Cisco APIC 6.0(8f)M - 应用策略基础设施控制器

Cisco APIC 6.0(8f)M - 应用策略基础设施控制器Cisco APIC 6.0(8f)M - 应用策略基础设施控制器 Application Policy Infrastructure Controller (APIC) 请访问原文链接:https://sysin.org/blog/cisco-apic-6/ 查看最新版。原创作品,转载请保留出处。 作者主页:sysin.org思科…

python--成功案例

https://www.python.org/about/success/

特斯拉 Model Y 焕新版 vs Model 3 焕新版 All In One

特斯拉 Model Y 焕新版 vs Model 3 焕新版 All In One 减配 LED 氛围灯 Model Y 焕新版, 车门上的 LED 灯带变短了 💩特斯拉 Model Y 焕新版 vs Model 3 焕新版 All In One 减配 LED 氛围灯 Model Y 焕新版, 车门上的 LED 灯带变短了 💩https://www.tesla.cn/modely/design…

FunPapers[1]: GBDT和DNN强强联手,表格预测新突破!

论文提出了Tree-hybrid MLP(T-MLP)方法,其核心思想是结合GBDT的特征选择和模型集成优势与DNN的高维特征空间和光滑优化特性,通过张量化GBDT特征门、DNN架构剪枝和反向传播协同训练MLP模型,以实现高效、有效的表数据预测。Team up GBDTs and DNNs: Advancing Efficient and…

如何迁移wsl发行版

转载:轻松搬迁!教你如何将WSL从C盘迁移到其他盘区,释放存储空间! - 知乎 1.准备工作 打开CMD,输入wsl -l -v查看wsl虚拟机的名称与状态。wsl虚拟机的名称与状态 了解到本机的WSL全称为Ubuntu-22.04,以下的操作都将围绕这个来进行。 输入 wsl --shutdown 使其停止运行,再…

Hetao P1307 树的剖分 题解 [ 蓝 ] [ 树形 dp ] [ 贪心 ]

运用到了奇偶性拼凑答案 trick 的性质树形 dp 加上一点贪心。树的剖分:很厉害的性质题,代码也很好写。运用到了奇偶性拼凑答案的 trick。观察 首先发现一个很重要的条件:一个点的点权只可能是 \(0,1,2\)。 这个条件开始我们可能无法用上,于是先想最后的结果应该是怎样的。 …

常见的7种排序算法(转载)

本文介绍了七种常见的排序算法:冒泡排序、选择排序、插入排序、希尔排序、快速排序、归并排序和堆排序。每种算法通过具体步骤和代码实现进行详细讲解,包括时间复杂度分析。文中提供了丰富的示例代码和图解,帮助读者更好地理解各排序算法的工作原理及应用场景。【版权声明】…

【MySQL】MySQL为什么 不用 Docker部署?

MySQL为什么不推荐使用Docker部署 docker可以从远程仓库拉取镜像然后通过镜像快速的部署应用,非常的方便快捷, 但是 , 为什么 一般公司的 Mysql 不用docker部署,而是部署在 物理机器上呢? 一、DB有状态,不方便扩容1.1 Docker容器的两大类型: 有状态 、无状态的区分1.2 My…

RocketMQ实战—1.订单系统面临的技术挑战

大纲 1.一个订单系统的整体架构、业务流程及负载情况 2.订单系统面临的技术问题一:下订单的同时还要发券、发红包、Push推送等导致性能太差 3.订单系统面临的技术问题二:订单退款时经常流程失败导致无法完成退款 4.订单系统面临的技术问题三:第三方客户系统的对接耦合性太高…

【docker】自建 docker 镜像加速

1. 背景 由于神秘原因,国内用户逐渐无法访问 Docker Hub 仓库。这对于开发者来说是个不小的难题。而这个解决方案是通过赛博菩萨 cloudflare(简称 CF)中转请求,解决访问限制并加速访问。刚好之前分享了如何获取免费域名,今天就来分享一下如何用免费域名在CF部署自己的 doc…

hive--MySQL8错误--ERROR 1410 (42000): You are not allowed to create a user with GRANT

错误展示:解决办法: create user hadoopguide@localhost identified by hadoopguide; grant all privileges on hadoopguide.* to hadoopguide@localhost with grant option; 效果图:错误原因: MySQL 8.0 及以上版本不允许在授予权限时隐式创建用户。所以显式创建用户后再授…

【Linux】Linux一键切换镜像源,告别慢速下载,国内镜像让你飞起来!

简介 本文教你如何通过一键切换 Linux 镜像源,快速提高系统软件包的下载速度,告别“慢”的困扰,轻松提升体验。 项目地址:https://github.com/SuperManito/LinuxMirrors 官方文档:https://linuxmirrors.cn/开头 Linux 系统在安装和更新软件时,经常会遇到速度慢的问题,尤…