|
| 1 | +# .NET 每周分享第 39 期 |
| 2 | + |
| 3 | +## 卷首语 |
| 4 | + |
| 5 | +`C#` 12 的新功能预览 |
| 6 | + |
| 7 | +1. `nameof` 可以访问成员变量,包括初始化,静态变量和注解 |
| 8 | +2. 内联数组: 可以标记 `[System.Runtime.CompilerServices.InlineArray(10)]` 方式在内存中创建 10 个元素 |
| 9 | +3. `Interceptors`: 这是一项实验功能,可以让特定的方法调用路由到不同的代码。 |
| 10 | + |
| 11 | +## 行业资讯 |
| 12 | + |
| 13 | +1、[Azure AD 改名 Microsoft Entra](https://devblogs.microsoft.com/dotnet/azure-ad-microsoft-entra/) |
| 14 | + |
| 15 | + |
| 16 | + |
| 17 | +微软改名部继续工作,将 `Azure AD` 改名为 `Microsoft Entra`。这个改名对于 `.NET` 开发者而言,没有任何影响。 |
| 18 | + |
| 19 | +## 文章推荐 |
| 20 | + |
| 21 | +1、[最小的 Hello world 程序](https://blog.washi.dev/posts/tinysharp/) |
| 22 | + |
| 23 | + |
| 24 | + |
| 25 | +`Hello World` 是每个编程语言的第一个 demo, 那么对于 `C#` 而言,一个简单的 `Hello world` 应用程序会暂用多大的磁盘空间呢? |
| 26 | + |
| 27 | +```csharp |
| 28 | +using System; |
| 29 | +namespace ConsoleApp1; |
| 30 | +internal static class Program |
| 31 | +{ |
| 32 | + public static void Main(string[] args) |
| 33 | + { |
| 34 | + Console.WriteLine("Hello, World!"); |
| 35 | + } |
| 36 | +} |
| 37 | +``` |
| 38 | + |
| 39 | +这段代码在 `.NET Framework 4.7.2` 版本下, 生成可执行文件的大小为 `5kb`,那么作者是如何一步步将它减少体积的呢? |
| 40 | + |
| 41 | +- 移除 `Nullable` 属性 |
| 42 | +- 手动创建 `.NET Module` |
| 43 | +- 移除引入和基础重定位 |
| 44 | +- 删除名称 metadata |
| 45 | +- 删除更多的 metadata |
| 46 | +- 不适用 `System.Console.WriteLine` |
| 47 | + |
| 48 | +最终的 `Hello World` 文件大小定格在 476 字节。 |
| 49 | + |
| 50 | + |
| 51 | +2、[分析器认为有错误写法](https://www.youtube.com/watch?v=v7GAQfWnco0&ab_channel=NickChapsas) |
| 52 | + |
| 53 | + |
| 54 | + |
| 55 | +在 `.NET 8` 中, `Code Analysis` 为 C# 代码更多的检查,比如下面 6 种例子 |
| 56 | + |
| 57 | +1. ConstantExpected |
| 58 | + |
| 59 | +```csharp |
| 60 | +public static void ProcessValue([ConstantExpected (Min = 20)]int value) |
| 61 | +{ |
| 62 | +} |
| 63 | +``` |
| 64 | +如果方法的参数增加了 `ConstantExpected` 的注解,那么要求方法的调用必须传入常量 |
| 65 | + |
| 66 | +2. Linq 中的 Any |
| 67 | + |
| 68 | +```csharp |
| 69 | +public static bool HasElement(string[] strings) |
| 70 | +{ |
| 71 | + return strings.Any(); |
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +通常 `Linq` 中的 `Any()` 方法可以判断是否存在元素,但是如果是具体的类型,比如 `Array`, `List` 等,则分析器提示使用 `strings.Length !=0` 的方式。 |
| 76 | + |
| 77 | +3. Split |
| 78 | + |
| 79 | +```csharp |
| 80 | +public static string[] SplitText(string text) |
| 81 | +{ |
| 82 | + return text.Split(new char[] { ' ', ',' }); |
| 83 | +} |
| 84 | +``` |
| 85 | + |
| 86 | +在这个方法中,每次调用 `SplitText` 方法,都会创建一个 `char[]` 对象,这样增加了内存压力,所以分析器建议将 array 对象设置为一个只读对象,并且只初始化一次。 |
| 87 | + |
| 88 | + |
| 89 | +4. Cast |
| 90 | + |
| 91 | +```csharp |
| 92 | +var test = new int[] { 1 }.Cast<string>(); |
| 93 | +``` |
| 94 | + |
| 95 | +这个代码在运行的时候会抛出 `InvalidCastExpection` ,现在分析器会检测到这个错误并且在编译的时候给出这样提示。 |
| 96 | + |
| 97 | + |
| 98 | +在 Runtime repo 中的 [issue](https://github.com/dotnet/runtime/issues/78442),它列出可以增加的分析功能。 |
| 99 | + |
| 100 | +3、[你应该学习 Blazor 吗?](https://www.youtube.com/watch?v=OUUlO8fQOfE&ab_channel=IAmTimCorey) |
| 101 | + |
| 102 | + |
| 103 | + |
| 104 | +`Blazor` 的未来是怎样的?我们需要学习吗?这个视频给了一些介绍。 |
| 105 | + |
| 106 | +4、[高效连接字符串和字符](https://www.meziantou.net/micro-optimization-concatenating-a-string-with-a-char-using-string-concat.htm) |
| 107 | + |
| 108 | + |
| 109 | + |
| 110 | +C# 中字符串的 `+` 操作符是转换为 `string.Concat` 方法调用。如果是一个字符串连接一个字符呢?有没有什么其他方法,并且能够使用更高的性能呢? |
| 111 | +`string.Concat` 有另一个方法签名,接受的参数类型是 `ReadOnlySpan<Char>`, 所以只需要将 `char` 类型转化为 `ReadOnlySpan<char>` 即可 |
| 112 | + |
| 113 | +```csharp |
| 114 | +[MemoryDiagnoser] |
| 115 | +public class StringConcatBenchmark |
| 116 | +{ |
| 117 | + private string Part1 = "abc"; |
| 118 | + private char Part2_Char = 'd'; |
| 119 | + private string Part2_String = "d"; |
| 120 | + private string Part3 = "efg"; |
| 121 | + |
| 122 | + [Benchmark(Baseline = true)] |
| 123 | + public string Operator_String_Char_String() => Part1 + Part2_Char + Part3; |
| 124 | + |
| 125 | + [Benchmark] |
| 126 | + public string Operator_String_String_String() => Part1 + Part2_String + Part3; |
| 127 | + |
| 128 | + [Benchmark] |
| 129 | + public string String_Concat() => string.Concat(Part1, new ReadOnlySpan<char>(in Part2_Char), Part3); |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +benchmark 结果如下 |
| 134 | + |
| 135 | + |
| 136 | +5、[Powershell 定制自动任务](https://www.techtarget.com/searchwindowsserver/tutorial/Learn-how-to-create-a-scheduled-task-with-PowerShell) |
| 137 | + |
| 138 | + |
| 139 | + |
| 140 | +如果我们想要在 Widnows 中注册一个定时任务,使用 PowerShell 那么该怎么做呢? |
| 141 | + |
| 142 | +1. 注册一个Action |
| 143 | +```powershell |
| 144 | +$Action = New-ScheduledTaskAction -Execute 'pwsh.exe' -Argument '-NonInteractive -NoLogo -NoProfile -File "C:\MyScript.ps1"' |
| 145 | +``` |
| 146 | +首先通过 `New-ScheduledTaskAction` 注册一个 Action,它知名了执行的命令和相关的参数 |
| 147 | + |
| 148 | +2. 定制一个 Trigger |
| 149 | + |
| 150 | +```powershell |
| 151 | +$Trigger = New-ScheduledTaskTrigger -Once -At 3am |
| 152 | +``` |
| 153 | + |
| 154 | +定义一个 `Trigger` 用来什么时候启动这个任务,比如每次早上 3 点运行 |
| 155 | + |
| 156 | +3. 配置 |
| 157 | + |
| 158 | +```csharp |
| 159 | +$Settings = New-ScheduledTaskSettingsSet |
| 160 | +``` |
| 161 | +通过从 `Setting` 配置任务的额外信息,比如执行超时,是否 retry 等等。 |
| 162 | + |
| 163 | +4. 创建 Task |
| 164 | + |
| 165 | +```csharp |
| 166 | +$Task = New-ScheduledTask -Action $Action -Trigger $Trigger -Settings $Settings |
| 167 | +``` |
| 168 | +根据 `Action`, `Trigger` 和 `Setting` 创建要给 Task |
| 169 | + |
| 170 | +5. 注册任务 |
| 171 | + |
| 172 | +```csharp |
| 173 | +Register-ScheduledTask -TaskName 'My PowerShell Script' -InputObject $Task |
| 174 | +``` |
| 175 | +目前任务是保存在内存中,通过 `Register-ScheduleTask` 将这个对象注册到操作系统中。 |
| 176 | + |
| 177 | +6、[写出 Clean Code 的 8 个技巧](https://www.milanjovanovic.tech/blog/8-tips-to-write-clean-code) |
| 178 | + |
| 179 | +如果有这样一段 C# 代码,该如何优化呢? |
| 180 | + |
| 181 | +```csharp |
| 182 | +public void Process(Order? order) |
| 183 | +{ |
| 184 | + if (order != null) |
| 185 | + { |
| 186 | + if (order.IsVerified) |
| 187 | + { |
| 188 | + if (order.Items.Count > 0) |
| 189 | + { |
| 190 | + if (order.Items.Count > 15) |
| 191 | + { |
| 192 | + throw new Exception( |
| 193 | + "The order " + order.Id + " has too many items"); |
| 194 | + } |
| 195 | + |
| 196 | + if (order.Status != "ReadyToProcess") |
| 197 | + { |
| 198 | + throw new Exception( |
| 199 | + "The order " + order.Id + " isn't ready to process"); |
| 200 | + } |
| 201 | + |
| 202 | + order.IsProcessed = true; |
| 203 | + } |
| 204 | + } |
| 205 | + } |
| 206 | +} |
| 207 | +``` |
| 208 | + |
| 209 | +那么 8 个建议重构更好的代码 |
| 210 | +- 尽早返回 |
| 211 | +- 合并更多的 If 判断条件 |
| 212 | +- 使用 `Linq` 让代码更加简洁 |
| 213 | +- 使用特定的方法来重构 If 判断条件 |
| 214 | +- 抛出自定义异常 |
| 215 | +- 将魔数转换为常量 |
| 216 | +- 将字符串转化为枚举类型 |
| 217 | +- 使用返回结果类型,而不是直接修改输入 |
| 218 | + |
| 219 | +7、[如何正确的使用 HttpClient 类](https://www.milanjovanovic.tech/blog/the-right-way-to-use-httpclient-in-dotnet) |
| 220 | + |
| 221 | + |
| 222 | + |
| 223 | +`HttpClient` 类是 `C#` 代码中广泛使用的类型,那么该怎么最好的使用这个类型呢? |
| 224 | + |
| 225 | +1. 普通方式 |
| 226 | + |
| 227 | +```csharp |
| 228 | + var client = new HttpClient(); |
| 229 | +//... |
| 230 | +``` |
| 231 | + |
| 232 | +2. 使用 `IHttpClientFactory` |
| 233 | + |
| 234 | +```csharp |
| 235 | +var client = _factory.CreateClient(); |
| 236 | +//.... |
| 237 | +``` |
| 238 | + |
| 239 | +`Microsoft.Extensions.Http` 推荐的使用方式 |
| 240 | + |
| 241 | + |
| 242 | +3. Name Client |
| 243 | + |
| 244 | +```csharpservices.AddHttpClient("github", (serviceProvider, client) => |
| 245 | +{ |
| 246 | + var settings = serviceProvider |
| 247 | + .GetRequiredService<IOptions<GitHubSettings>>().Value; |
| 248 | + client.BaseAddress = new Uri("https://api.github.com"); |
| 249 | +}); |
| 250 | +
|
| 251 | + var client = _factory.CreateClient("github"); |
| 252 | +``` |
| 253 | + |
| 254 | +3. Type client |
| 255 | + |
| 256 | +```csharp |
| 257 | +services.AddHttpClient<GitHubService>((serviceProvider, client) => |
| 258 | +{ |
| 259 | + var settings = serviceProvider |
| 260 | + .GetRequiredService<IOptions<GitHubSettings>>().Value; |
| 261 | + client.BaseAddress = new Uri("https://api.github.com"); |
| 262 | +}); |
| 263 | + |
| 264 | +public class GitHubService |
| 265 | +{ |
| 266 | + private readonly HttpClient client; |
| 267 | + |
| 268 | + public GitHubService(HttpClient client) |
| 269 | + { |
| 270 | + _client = client; |
| 271 | + } |
| 272 | + |
| 273 | + public async Task<GitHubUser?> GetUserAsync(string username) |
| 274 | + { |
| 275 | + GitHubUser? user = await client |
| 276 | + .GetFromJsonAsync<GitHubUser>($"users/{username}"); |
| 277 | + |
| 278 | + return user; |
| 279 | + } |
| 280 | +} |
| 281 | +``` |
| 282 | +这样在应用程序中,只需要在依赖注入框架中获取 `GithubSerivce` 对象即可,注意这是一个 `transient` 注入方式。 |
| 283 | + |
| 284 | +## 开源项目 |
| 285 | + |
| 286 | +1、[MSBuilder 编辑器](https://marketplace.visualstudio.com/items?itemName=mhutch.MSBuildEditor) |
| 287 | + |
| 288 | + |
| 289 | + |
| 290 | +`MSBuild Editor` 插件是用来管理 C# 的 MSBuild 文件,主要有下面几个功能 |
| 291 | + |
| 292 | +- 智能提示 |
| 293 | +- 导航 |
| 294 | +- 快速查询 |
| 295 | +- 验证和分析 |
| 296 | +- 重构和代码修复 |
| 297 | +- schemas 修改 |
| 298 | +- 导入 |
| 299 | + |
| 300 | +2、[TextUtility](https://github.com/PowerShell/TextUtility) |
| 301 | + |
| 302 | + |
| 303 | + |
| 304 | +`Microsoft.Powershell.TextUtility` 是 `Powershell` 中的文本处理库,只要包含一下三个函数 |
| 305 | + |
| 306 | +1. Compare-Text |
| 307 | + |
| 308 | +它实现了 [diff-match-path](https://github.com/google/diff-match-patch) |
| 309 | + |
| 310 | + |
| 311 | + |
| 312 | +2. ConvertFrom-Base64/ConvertTo-Base64 |
| 313 | + |
| 314 | +它主要是将字符串转换成 Base64 或者将 Base64 转成成字符串。 |
| 315 | + |
| 316 | +```powershell |
| 317 | +> ConvertTo-Base64 "dotnetweekly" |
| 318 | +# ZG90bmV0d2Vla2x5 |
| 319 | +> ConvertFrom-Base64 "ZG90bmV0d2Vla2x5" |
| 320 | +# dotnetweekly |
| 321 | +``` |
| 322 | + |
| 323 | +3. ConvertFrom-TextTable |
| 324 | + |
| 325 | +这个主要是将文本的表格转换成一个实体对象 |
| 326 | + |
| 327 | +```csharp |
| 328 | +> $string = $("a".."z";"A".."Z";0..9) -join "" |
| 329 | +> 1..10 | %{$string}|convertfrom-texttable -noheader -columnoffset 0,15,23,40,55 | ft |
| 330 | +``` |
| 331 | + |
| 332 | + |
0 commit comments