|
| 1 | +# .NET 每周分享第 57 期 |
| 2 | + |
| 3 | +## 卷首语 |
| 4 | + |
| 5 | + |
| 6 | + |
| 7 | +这是 Reddit 上的一个的[讨论](https://www.reddit.com/r/dotnet/comments/1da6brw/how_to_break_out_of_legacy_net_jobs/),提问者是一位在 `.NET Framework` 上工作超过 9 年的开发者。当他尝试投递其他更加现代的 `.NET` 或者 Azure 相关的开发工作的时候,在面试结束后,并没有得到后续的回复。他的问题是该如何从传统的 `.NET` 职位跳槽到新的 `.NET` 职位。 |
| 8 | +社区给了很多建议 |
| 9 | + |
| 10 | +- 假装拥有现代 `.NET` 的技能 |
| 11 | +- 切换语言并不是一件很罕见的事情,我们应该适应它 |
| 12 | +- 可以在工作中,将小的组件逐步迁移到现代的 `.NET` |
| 13 | +- ... |
| 14 | + |
| 15 | +## 行业资讯 |
| 16 | + |
| 17 | +1、[关于Blazor流行度的讨论](https://www.reddit.com/r/dotnet/comments/18q5sbr/blazor_popularity/) |
| 18 | + |
| 19 | + |
| 20 | + |
| 21 | +`Reddit` 上有一个 `Blazor` 流行度的讨论,总体而言,有不少人在生产环境中使用 `Blazor`,也有一些人在尝试之后就放弃了使用。 |
| 22 | + |
| 23 | +## 文章推荐 |
| 24 | + |
| 25 | +1、[Refit.NET个人实践](https://medium.com/medialesson/refit-net-my-personal-caller-best-practise-5e0b24ed6486) |
| 26 | + |
| 27 | + |
| 28 | + |
| 29 | +`Refit` 是 `.NET` 中比较著名的开源库。文章作者分享了自己在使用 `Refit` 的一些最佳实践。主要是不通过直接定义的接口来使用它,而是将它封装成另外一个服务,这样可以做额外的工作,比如认证,授权和异常处理。 |
| 30 | + |
| 31 | +```csharp |
| 32 | +public interface IMyHttpApiClient |
| 33 | +{ |
| 34 | + [Get("/users/{username}/repos")] |
| 35 | + Task<List<Repository>> GetUserRepositoriesAsync(string username); |
| 36 | +} |
| 37 | + |
| 38 | +// Creating a Refit client |
| 39 | +IMyHttpApiClient gitHubApi = RestService.For<IMyHttpApiClient >("https://api.github.com"); |
| 40 | + |
| 41 | +public class MyApiProvider(IMyHttpApiClient Client) |
| 42 | +{ |
| 43 | + public Task<UserResponseItem> GetUser(string user) |
| 44 | + { |
| 45 | + return Execute(() => Client.GetUser(user)); |
| 46 | + } |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +2、[掌握 Fluent Builder Design](https://www.youtube.com/watch?v=qCIr30WxJQw&ab_channel=MilanJovanovi%C4%87) |
| 51 | + |
| 52 | +`Builder Pattern` 是一种设计模式,通常有下面的特点 |
| 53 | + |
| 54 | +- 创造设计模式 |
| 55 | +- 简化构建复杂对象 |
| 56 | +- 简洁,更加直观的代码 |
| 57 | +- 复杂度增加,许多新类 |
| 58 | + |
| 59 | +在 `ASP.NET Core` 中依赖注入中包含了很多这样的设计,这里一个简单的 `C#` 例子来展示这个模式实现 |
| 60 | + |
| 61 | +```csharp |
| 62 | +var order = OrderBuilder.Empty.SetName("Order 1") |
| 63 | + .WithNumber(1) |
| 64 | + .ShipTo(s => s.City("São Paulo").ZipCode("01310-100")) |
| 65 | + .Build(); |
| 66 | + |
| 67 | +public class Order { |
| 68 | + public string Name { get; set; } |
| 69 | + |
| 70 | + public int Number{ get; set; } |
| 71 | + |
| 72 | + public Street Street { get; set; } |
| 73 | +} |
| 74 | + |
| 75 | +public class Street { |
| 76 | + public string City { get; set; } |
| 77 | + |
| 78 | + public string ZipCode { get; set; } |
| 79 | +} |
| 80 | + |
| 81 | +public class OrderBuilder { |
| 82 | + public static OrderBuilder Empty => new OrderBuilder(); |
| 83 | + |
| 84 | + private OrderBuilder() {} |
| 85 | + |
| 86 | + private string _name; |
| 87 | + private int _number; |
| 88 | + |
| 89 | + private StreetBuilder _streetBuilder = StreetBuilder.Empty; |
| 90 | + |
| 91 | + public OrderBuilder SetName(string name) { |
| 92 | + _name = name; |
| 93 | + return this; |
| 94 | + } |
| 95 | + |
| 96 | + public OrderBuilder WithNumber(int number) { |
| 97 | + _number = number; |
| 98 | + return this; |
| 99 | + } |
| 100 | + |
| 101 | + public OrderBuilder ShipTo(Action<StreetBuilder> buildStreet) { |
| 102 | + buildStreet(_streetBuilder); |
| 103 | + return this; |
| 104 | + } |
| 105 | + |
| 106 | + public Order Build() { |
| 107 | + return new Order{Name = _name, Number = _number, |
| 108 | + Street = _streetBuilder.Build()}; |
| 109 | + } |
| 110 | +} |
| 111 | + |
| 112 | +public class StreetBuilder { |
| 113 | + public static StreetBuilder Empty => new StreetBuilder(); |
| 114 | + |
| 115 | + private StreetBuilder() {} |
| 116 | + private string _city; |
| 117 | + private string _zipCode; |
| 118 | + |
| 119 | + public StreetBuilder City(string city) { |
| 120 | + _city = city; |
| 121 | + return this; |
| 122 | + } |
| 123 | + |
| 124 | + public StreetBuilder ZipCode(string zipCode) { |
| 125 | + _zipCode = zipCode; |
| 126 | + return this; |
| 127 | + } |
| 128 | + |
| 129 | + public Street Build() { return new Street{City = _city, ZipCode = _zipCode}; } |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +这里的 `Order` 是目标的对象,但是我们增加了 `OrderBuilder` 类,它的 `SetName` 和 `WithNumber` 两个方法是保存相关属性,而 `Street` 是另外个一个对象,所以我们有构建了 `StreetBuilder` 类,在 `OrderBuilder` 中使用接受一个 `Action<StreetBuilder>` 委托来创建这个属性,最后的 `Build` 方法把记录的数据和委托构造出一个 `Order` 对象。 |
| 134 | + |
| 135 | +3、[不可变字典比较](https://goatreview.com/choosing-best-immutable-dictionary-csharp-projects/) |
| 136 | + |
| 137 | +在 `C#` 中有 `ReadOnlyDictionary`, `ImmutableDictionary` 和 `ForzenDictionary` 三种类型,那么它们在功能和性能上有什么区别呢? |
| 138 | + |
| 139 | +- 构造 |
| 140 | + |
| 141 | +```csharp |
| 142 | +[Benchmark] |
| 143 | +public void Create_ReadOnlyDictionary() { |
| 144 | + var dictionnary = new Dictionary<string, Goat>(); |
| 145 | + for (int i = 0; i < 1000000; i++) { |
| 146 | + dictionnary.Add(i.ToString(), new Goat{Name = i.ToString()}); |
| 147 | + } |
| 148 | + var result = new ReadOnlyDictionary<string, Goat>(dictionnary); |
| 149 | +} |
| 150 | + |
| 151 | +[Benchmark] |
| 152 | +public void Create_ImmutableDictionary() { |
| 153 | + var dictionnary = new Dictionary<string, Goat>(); |
| 154 | + for (int i = 0; i < 1000000; i++) { |
| 155 | + dictionnary.Add(i.ToString(), new Goat{Name = i.ToString()}); |
| 156 | + } |
| 157 | + var result = dictionnary.ToImmutableDictionary(); |
| 158 | +} |
| 159 | + |
| 160 | +[Benchmark] |
| 161 | +public void Create_FrozenDictionary() { |
| 162 | + var dictionnary = new Dictionary<string, Goat>(); |
| 163 | + for (int i = 0; i < 1000000; i++) { |
| 164 | + dictionnary.Add(i.ToString(), new Goat{Name = i.ToString()}); |
| 165 | + } |
| 166 | + var result = dictionnary.ToFrozenDictionary(); |
| 167 | +} |
| 168 | +``` |
| 169 | + |
| 170 | +Benchmark 的结果如下 |
| 171 | + |
| 172 | +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | |
| 173 | +|--------------------------- |-----------:|------------:|------------:|---------:|---------:|---------:|----------:| |
| 174 | +| Create_ReadOnlyDictionary | 967.4 us | 742.6 us | 40.70 us | 220.7031 | 220.7031 | 220.7031 | 1.72 MB | |
| 175 | +| Create_ImmutableDictionary | 6,556.5 us | 27,352.5 us | 1,499.28 us | 218.7500 | 218.7500 | 218.7500 | 2.33 MB | |
| 176 | +| Create_FrozenDictionary | 2,985.9 us | 7,518.3 us | 412.10 us | 359.3750 | 359.3750 | 359.3750 | 2.6 MB | |
| 177 | + |
| 178 | +可以看出 `ReadOnlyDictionary` 创建最快,`FrozenDictionary` 次之,最后是 `ImmutableDictionary` 最慢。 原因是什么呢? `ReadOnlyDictionary` 是复用了底层 `Dictionary` 结构,只不过丢弃了修改的操作,比如 `Add` 等,所以如果还能访问底层的 `Dictionary`, 也是有机会去修改 `ReadOnlyDictionary`; `ImmutableDictionary` 是 `Immutable` 的实现,当你在修改这个字典的时候,它会创建一个新的字典,而保持原有的不变; |
| 179 | + |
| 180 | +- 读取 |
| 181 | + |
| 182 | +```csharp |
| 183 | +[Benchmark] |
| 184 | +public void TryGetValue_ReadOnlyDictionary() { |
| 185 | + for (int i = 0; i < 1000000; i++) { |
| 186 | + var index = Random.Shared.Next(0, N); |
| 187 | + _readOnlyDictionary.TryGetValue(index.ToString(), out var value); |
| 188 | + } |
| 189 | +} |
| 190 | + |
| 191 | +[Benchmark] |
| 192 | +public void TryGetValue_ImmutableDictionary() { |
| 193 | + for (int i = 0; i < 1000000; i++) { |
| 194 | + var index = Random.Shared.Next(0, N); |
| 195 | + _immutableDictionary.TryGetValue(index.ToString(), out var value); |
| 196 | + } |
| 197 | +} |
| 198 | + |
| 199 | +[Benchmark] |
| 200 | +public void TryGetValue_FrozenDictionary() { |
| 201 | + for (int i = 0; i < 1000000; i++) { |
| 202 | + var index = Random.Shared.Next(0, N); |
| 203 | + _frozenDictionary.TryGetValue(index.ToString(), out var value); |
| 204 | + } |
| 205 | +} |
| 206 | +``` |
| 207 | + |
| 208 | +Benchmark 结果如下 |
| 209 | + |
| 210 | +| Method | Mean | Error | StdDev | Gen0 | Allocated | |
| 211 | +|-------------------------------- |-----------:|-----------:|----------:|--------:|----------:| |
| 212 | +| TryGetValue_ReadOnlyDictionary | 676.7 us | 502.6 us | 27.55 us | 31.2500 | 303.14 KB | |
| 213 | +| TryGetValue_ImmutableDictionary | 1,514.5 us | 845.4 us | 46.34 us | 31.2500 | 303.12 KB | |
| 214 | +| TryGetValue_FrozenDictionary | 391.3 us | 1,828.4 us | 100.22 us | 32.7148 | 303.13 KB | |
| 215 | + |
| 216 | +可以看出 `ReadOnlyDictionary` 创建最快,`FrozenDictionary` 次之,最后是 `ImmutableDictionary` 最慢。之前我们讨论 `ImmutableDictionary` 是不可变的,可以在多线程中使用。但是 `FrozenDictionary` 也是不可变的字段,但是它在读取的数据的性能上比 `ImmutableDicionary` 好很多。 |
| 217 | + |
| 218 | +所以,我们可以得到结论,在多线程环境中, `ReadOnlyDictionary` 并不是好的类型,`ImmutableDictionary` 可以在多线程写操作中发挥作用,`FrozenDictionary` 在多线程读操作中发挥作用。 |
| 219 | + |
| 220 | +4、[Type Alias](https://devblogs.microsoft.com/dotnet/refactor-your-code-using-alias-any-type/) |
| 221 | + |
| 222 | + |
| 223 | + |
| 224 | +这是 C# 新功能介绍的一系列文章,主要介绍了 `Alias Any Type` 的功能。不同于之前的功能介绍,这次作者按照一个具体的 demo 展示如何使用这个功能,大部分内容在 `GlobalUsing.cs` 文件中 |
| 225 | + |
| 226 | +```csharp |
| 227 | +// Ensures that all types within these namespaces are globally available. |
| 228 | +global using Alias.AnyType; |
| 229 | +global using Alias.AnyType.Extensions; |
| 230 | +global using Alias.AnyType.ResponseModels; |
| 231 | + |
| 232 | +// Expose all static members of math. |
| 233 | +global using static System.Math; |
| 234 | + |
| 235 | +// Alias a coordinates object. |
| 236 | +global using Coordinates = (double Latitude, double Longitude); |
| 237 | + |
| 238 | +// Alias representation of degrees-minutes-second (DMS). |
| 239 | +global using DMS = (int Degree, int Minute, double Second); |
| 240 | + |
| 241 | +// Alias representation of various distances in different units of measure. |
| 242 | +global using Distance = (double Meters, double Kilometers, double Miles); |
| 243 | + |
| 244 | +// Alias a stream of coordinates represented as an async enumerable. |
| 245 | +global using CoordinateStream = System.Collections.Generic.IAsyncEnumerable< |
| 246 | + Alias.AnyType.CoordinateGeoCodePair>; |
| 247 | + |
| 248 | +// Alias the CTS, making it simply "Signal". |
| 249 | +global using Signal = System.Threading.CancellationTokenSource; |
| 250 | +``` |
| 251 | + |
| 252 | +- `global using Alias.AnyType` 可以导入这个 `namespace` 下所有成员 |
| 253 | +- `global using static System.Match` 可以导出这个命令空间下的静态成员 |
| 254 | +- `global using DMS = (int Degree, int Minute, double Second)` 将一个 `Tuple` 类型使用 `DMS` 别名 |
| 255 | +- `global using Signal = System.Threading.CancellationTokenSource` 是将一个系统类型 `CancellationTokenSource` 使用 `Signal` 别名 |
| 256 | + |
| 257 | +5、[Primary Constructor 的优和劣](https://andrewlock.net/thoughts-about-primary-constructors-3-pros-and-5-cons/) |
| 258 | + |
| 259 | +`Primary Constructor` 是 `C#` 12 引入的新的语法糖,其中的好和坏作者都做出的比较 |
| 260 | + |
| 261 | +**优势** |
| 262 | + |
| 263 | +1. 基础字段的初始化 |
| 264 | +2. 简化单元测试类的初始化 |
| 265 | +3. MVC controller 中的依赖注入 |
| 266 | + |
| 267 | +**劣势** |
| 268 | + |
| 269 | +1. 字段和参数冲突 |
| 270 | +2. 没有办法标记 `readonly` |
| 271 | +3. `Struct` 类型没法控制内存分布 |
| 272 | +4. 命名规则冲突 |
| 273 | + |
| 274 | +6、[异常中的 HResult](https://blog.elmah.io/understanding-the-exception-hresult-property-in-c/) |
| 275 | + |
| 276 | + |
| 277 | + |
| 278 | +在 C# 异常处理中,通常我们只会关心 `Message`, `Stack Trace` 等等内容,那么 `HResult` 这段字段代表什么意思呢? |
| 279 | +首先 `HResult` 是 `Handle to result` 的简写,它是标准的在不同组件之间交流的错误信息的方式,它是一个 32 位的整型数据,包含了三个内容 |
| 280 | + |
| 281 | +- Severity |
| 282 | +- Facility |
| 283 | +- Code |
| 284 | + |
| 285 | +```csharp |
| 286 | +try |
| 287 | +{ |
| 288 | + int a = 1; |
| 289 | + int b = 0; |
| 290 | + int c = a / b; |
| 291 | +} |
| 292 | +catch (System.Exception e) |
| 293 | +{ |
| 294 | + var isFailure = (e.HResult & 0x80000000) != 0; // isFailure is true |
| 295 | + var facility = (e.HResult & 0x7FFF0000) >> 16; // facility is 2 |
| 296 | + var code = (e.HResult & 0xFFFF); // code is 18 |
| 297 | + System.Console.WriteLine($"isFailure: {isFailure}, facility: {facility}, code: {code}"); |
| 298 | +} |
| 299 | +``` |
| 300 | + |
| 301 | +你也可以去 [www.hresult.info](https://www.hresult.info/) 这个网站查询详细信息 |
| 302 | + |
| 303 | +## 开源项目 |
| 304 | + |
| 305 | +1、[Rin](https://github.com/mayuki/Rin) |
| 306 | + |
| 307 | + |
| 308 | + |
| 309 | +`Rin` 是一个 `ASP.NET Core` 的开源插件,它可以记录和展示每个 `Web API` 请求和响应的细节,以及时间线和 Trace 日志。 |
| 310 | + |
| 311 | + |
| 312 | + |
| 313 | +2、[AWS .NET Developer Guides](https://github.com/aws-samples/aws-net-guides) |
| 314 | + |
| 315 | + |
| 316 | + |
| 317 | +如果你想在 `AWS` 上开发和部署自己的 `.NET` 应用程序,那么这个开源项目可以参考一下,它包含了几乎大部分 `AWS` 的服务,比如通信,容器化,部署,监控,存储等等。 |
| 318 | + |
| 319 | +3、[OpenAI-dotnet](https://github.com/openai/openai-dotnet) |
| 320 | + |
| 321 | + |
| 322 | + |
| 323 | +OpenAI 出品的 `.NET` SDK, 支持各种模型。 |
0 commit comments