跳转至

C#开发踩坑总结

备注:以下是本人在使用C#开发一些项目时遇到的坑的汇总,以下解决思路仅代表个人观点,仅供参考

环境配置问题

1、.NET SDK问题

我在这里中提到使用.NET 6开发WPF会出现大量奇怪的报错,但是最近又莫名其妙的解决了,而当我尝试将原来的.NET 6 runtime 6.0.29升级到6.0.30,这个问题又会出现了。可能我的这种情况属于个例,但至少借此发现.NET SDK升级版本需要谨慎,有可能会出现一些奇怪的兼容等问题导致项目出现问题

2、下载/安装第三方库问题

在尝试运行某些开源项目之前会将涉及到的第三方库给提前下载配置好,但依然有可能会出现无法启动的情况,因为有些项目文件对第三方库的版本有一定的要求,有的必须大于某个版本号,有的必须使用特定版本的,因此如果是离线运行这些开源项目的时候,最好按照项目文件的要求,严格下载对应版本的第三方库以避免一些意外的错误

3、关于.NET 版本问题

近期在配置某些开源项目的时候,会看到类似net6.0-windows7net6.0-windows10.0.18362.0等,一开始我还以为这不就是.NET 6项目吗?就顺手将这些.NET版本切换到net6.0-windows,有的项目依然可以正常运行。 直到配置某个开源项目的时候,意外发现必须写成net6.0-windows10.0.18362.0这种形式才能运行,后来查看MSDN文档对这种写法有了一点了解,简单地说,加上具体的版本号可以告诉.NET,这个项目会用到Windows的某些API,因此这种情况下需要单独指定某个版本才行,具体可以参考这里

关于指定版本

按照MSDN的介绍,net6.0-windows10.0.18362.0:如果应用面向 Windows 10 版本 1903,但是我测试发现可以在Windows 10 1809上运行,不一定只能在1903及以上的系统上运行,不清楚这两者之间是否存在强关联,由于测试的次数不多,所以还需要更多的测试来确定该问题

4、慎用.NET 3.5

前段时间因着某个需求准备开发一个小工具,考虑到平台兼容性,打算基于.NET 3.5开发,结果很多第三方库都是.NET 4.5起步,像微软的很多库都要求.NET 4.6.2起步,导致有些库不得不想办法降级、魔改才能适应.NET 3.5,结果最后测试发现,可能这个库是个人的学习项目吧,因着涉及到底层的一些特性导致程序出现溢出异常,可是自己也没那个能力去处理,最后只能放弃了,如果换用.NET 4.5及以上的版本,可能就不至于遇到这样闹心的问题了,但是这让Windows 7的用户使用起来麻烦一些,所以感觉这方面有些无解

另外,在使用.NET 3.5开发WPF的时候也遇到一些问题,可能是首次引入WPF以及一些特性,导致没有热重载,出现崩溃无法准确定位错误位置,部分控件(比如DataGrid等)不支持.NET 3.5等,导致开发WPF应用感觉有些闹心

如果要使用.NET 3.5开发桌面应用的话,个人认为最好还是使用winform,同时在现在这样的大环境下,需要做好“自研”的准备了

5、C#语法和.NET版本的对应关系

虽然在MSDN中会提到发布了某个版本的.NET的同时更新了C#的语法,但是实际开发过程中发现在低版本的.NET中也可以使用高版本的C#语法,比如在.NET 4.6.2上可以使用C#10语法,只要不涉及到.NET本身的特性就可以

请注意

通过这几天开发,发现有些新的C#语法是无法在.NET Framework上使用,包括但不限于

比如从.NET 5及以后版本可以给属性增加init,类似于public string Name {get;init;},这样可以实现在初始化对象的时候给Name赋值,之后就不能在外部修改Name了,但是这个语法在.NET Framework上用不了,具体可以参考这个帖子解决问题

比如还有可以在接口中实现一些方法,例如

C#
public interface IStudent{
  public string Name;

  public void ShowName(){
    Console.WriteLine(Name);
  }

}
这样设计感觉会使人有些误会,感觉这个和抽象方法没有太多的区别啊,不过这样设计与抽象类还有什么具体的区别暂时不太清楚,稍后再仔细研究看看。不过这个语法也只能在.NET上使用,.NET Framework上会报错,会提示只能写成public void ShowName();,这个暂时没找到解决办法,目前只有通过写抽象类来解决了

目前来看,如果是属于语法糖级别的新语法,旧版的.NET可以直接用新语法,但是如果涉及到一些.NET库的话,就美哦与办法用这些新语法了,只能升级到最新版本的.NET,或者等有没有人将这些功能移植到旧版本的.NET

不过目前来看,要想实现这个效果,还是需要安装最新版本的VS和.NET SDK,安装好之后就可以实现在低版本的.NET上使用高版本的C#语法

需要提醒一下,目前测试发现在Rider中有GUI界面进行调整,但是在VS上需要手动修改csproj文件才行

6、留意构建类型

支持C#、C++、Rust等语言的IDE会有一个构建类型,一种是Debug,一种是Release,这两个是有一些区别的,前者为了方便调试,不会对二进制文件做优化,以便快速构建,同时会有比较丰富的调试信息,以便快速方便的调试程序,但是性能不一定达到最好;而Release则反过来,它会尽可能优化代码,以达到最佳性能,但是会丢失一些调试信息,至于构建速度是否有影响,至少在C#开发的时候倒没有明显的感知,但是听说C++/Rust会有比较明显的构建速度变慢的情况

以下是测试结果(虽然没啥意思)

测试代码

C#
var startTime = DateTime.Now;
ulong total = 0;
for (ulong i = 0; i < 10000000000; i++)
{
    total += i;
}

var endTime = DateTime.Now;
Console.WriteLine($"结果:{total}");
Console.WriteLine($"耗时:{endTime - startTime}");
.NET版本 Debug模式 Release模式
.NET 8 00:00:18.2833774 00:00:02.6693214
.NET 4.6.2 00:00:19.1123335 00:00:06.4351510
备注

在测试的时候发现一个有意思的事情,在Rider中,如果先使用.NET 8,然后在选项中切换到net462,在Release模式下得到的结果是2秒,但是如果使用.NET Framework模板创建.NET 4.6.2,在Release模式下得到的结果是6秒,不清楚为什么是这个结果

IDE问题

1、 IDE选择问题

目前用于C#开发的IDE主要有VS和Rider,这两个IDE各有其优缺点,至少在现阶段这两个IDE的一些功能特性和其优缺点导致没有办法去做取舍,只能都留着(这也导致有的时候开发一些项目,为了开发时体验更好,就需要同时开两个IDE,不仅占用大量内存,切换IDE其实也不是太方便)

以下是我总结的这两个IDE的优缺点

优点

Rider Visual Studio
稳定性更好 WPF开发有热重载
代码静态分析较好 有更丰富的项目模板
报错信息分析相对直观一点

缺点

Rider Visual Studio
WPF开发不支持热重载 卡(尤其安装了resharper后)
有些场景下代码提示过多,看着有些烦 代码静态分析较差(即使安装了reshaper)
项目模板太少(比如不能开发WinUI,程序清单配置缺失等) 无法近乎实时读写代码文件
部分细节功能被砍(比如无法按Alt一次性选择多行等)

虽然以上看着Rider的优点不如VS,缺点比VS多,但是有些优点或缺点并不是主要问题,目前VS的主要问题还是太卡(补充:经过测试发现主要是安装了resharper导致的,按照网上的说法,我的开发机配置还行,内存也足够,不清楚为什么这么卡,目前只能禁用这个插件换来稍微流畅的体验了),尤其在有些场景下敲代码有肉眼可见的延迟感,还有的时候由于同时开两种IDE,如果在Rider上写的代码比较多,再切换到VS上就有可能导致写的代码出现问题导致报错,因此还要花不少时间去修复这些琐碎的问题

虽然我很想全流程使用Rider开发,但是WPF开发没有热重载,有些项目模板缺失等问题,对Rider来说也算是一个硬伤,之前去刷帖的时候看到WPF热重载问题很早就被提及了,但是几年后依然没有彻底解决这个问题,不知道这是为什么。还有一些细节上总感觉Rider的设计是为了迎合VS的,与Jetbrains的其他IDE的风格不一样,比如选择多行这一块,无论是在PyCharm还是在GoLand上都没有这个问题,但是在Rider上却有这个问题,不清楚这是为什么。

总的来说,如果要做WPF的UI部分开发的话,我个人还是会倾向于使用VS,热重载+静态页面显示这两个功能对于UI部分的开发是非常重要的,在不少情况下不需要盲猜我设计的UI哪里有问题等等;但如果涉及到逻辑代码部分的开发,我会倾向于使用Rider去编写,虽然没有VS的AI代码补全,但是有更佳的代码静态分析功能,个人认为会比AI代码补全更重要。现在jetbrains的不少IDE已经内置了AI代码补全了,不知道什么时候Rider也能内置这个功能

虽然VS有内置AI代码补全,还有resharper的插件,但是在我所在意的代码静态分析上依然没有达到或超过Rider,而且目前测试发现加入了resharper后,不仅运行变慢了,而且时不时还会出现卡死的问题等,至少目前并没有提供预期的效果

还有一点,这两个IDE加上.NET运行时,对内存的占用非常高,尤其多开了几个Rider/VS,或者再开一个虚拟机,就很容易达到内存上限从而出现死机,这一点也需要注意

2、Rider与.NET Framework

上面一条我提到Rider不支持Xaml热重载,其实Rider相对于VS还有一个区别,就是在.NET Framework上没有一些程序分析功能,比如内存分析、CPU分析等,只有使用.NET 5及以后的版本才有这个功能,另外,可能这个分析功能是在Rider 2024.1及以后的版本才加入的,之前的版本是否有我忘记了,不过秉持着用新不用旧的原则,能用新版就用新版吧

还有一个比较奇怪的问题,在Rider中如果之前创建的是.NET Framework项目,那么没有办法通过GUI界面升级到.NET项目;但是如果之前创建的是.NET项目,则可以换成.NET Framework项目,有些奇怪。所以如果有同时生成.NET Framework和.NET项目的话,可以考虑先选择.NET项目模板,然后通过GUI勾选.NET Framework的版本,或者需要手动修改csproj文件了

VS的话就不用想了,至少到现在在GUI界面上还不能像Rider那样可以同时选择多个版本的.NET,.NET项目只能选择.NET SDK,.NET Framework项目只能选择.NET Framework的不同版本,连C#语法都要通过手动修改csproj文件才行

3.离线安装Avalonia插件问题

近期想折腾给Rider和VS安装Avalonia插件,结果下载好对应版本的插件并安装后,前者直接提示缺少com.inte.javafx,后者提示无法访问go.microsoft.com,前者目前找到了解决办法,就是需要先下载并安装JavaFX_plugin,然后再安装avalonia插件即可,但是VS的插件实在没找到解决办法,后续再折腾看看

另外,如果需要安装avalonia的项目模板,可以这样做: 1. 去nuget.org上下载avalonia.templates的nupkg包 2. 运行下面的命令dotnet new install "avalonia.templates.11.0.10.1.nupkg",这样无论在VS还是在Rider上都能看到avalonia的项目模板了,当然这个也不仅限于avalonia,如果需要安装其他的项目模板,理论上都可以这么做,前提是这个nuget包是用于创建项目模板的,并不是所有的nuget包都能这么做

4. 交叉编译问题

我们都知道,从.NET Core开始,.NET就能跨平台了,有的时候我需要像Go那样实现交叉编译,即我可以在Windows上编译Linux上运行的程序,那我也没有那么多功夫创建Linux系统吧?近期折腾发现新版的.NET还确实有提供交叉编译的功能

因为我是离线开发的,所以需要提前配置好相关的nuget包,比如需要下面这些nuget包

备注

由于我本地配置的nuget包太多了,暂时没有时间一一测试具体需要哪些包,所以需要你自行测试

  • runtime.linux-x64.Microsoft.DotNet.ILCompiler.8.0.7.nupkg
  • microsoft.netcore.app.runtime.linux-x64.7.0.20.nupkg
  • microsoft.netcore.app.runtime.linux-x64.6.0.29.nupkg
  • microsoft.netcore.app.host.linux-x64.6.0.29.nupkg
  • microsoft.netcore.app.host.linux-x64.8.0.4.nupkg
  • microsoft.aspnetcore.app.runtime.linux-x64.6.0.29.nupkg
  • microsoft.aspnetcore.app.runtime.linux-x64.8.0.4.nupkg
  • microsoft.netcore.app.runtime.linux-x64.8.0.4.nupkg

如果你是在Rider上编译Linux上的程序,可以这么配置

rider发布

如果是在VS上编译的话,可以这么配置

VS发布

不过如果你要在VS上发布AOT程序,这一点没有Rider灵活,你要么修改csproj文件,要么在一开始创建的时候注意将AOT选项打勾

如果在构建的时候出现一些依赖错误,再根据需要自行补依赖,一般就没有问题了

测试发现如果需要编译成AOT应用或WPF应用,那么就不要勾选修改未使用的程序集等功能,容易导致报错。还有因为调整成自包含类型,虽然不需要单独安装.NET runtime了,但是体积却大不少,如果你是属于程序体积敏感的开发者的话,需要注意这个问题