Skip to content

Commit c9844d8

Browse files
committed
episode 57
1 parent 270b024 commit c9844d8

File tree

2 files changed

+324
-1
lines changed

2 files changed

+324
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
### 2024
1818

19-
**六月份** : [056](docs/episode-056.md) :high_brightness:
19+
**六月份** : [057](docs/episode-057.md) :high_brightness: | [第 056 期](docs/episode-056.md)
2020

2121
**五月份** : [第 055 期](docs/episode-055.md) | [第 054 期](docs/episode-054.md)
2222

docs/episode-057.md

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
# .NET 每周分享第 57 期
2+
3+
## 卷首语
4+
5+
![image](https://github.com/DotNETWeekly-io/DotNetWeekly/assets/11272110/599ea12b-ea74-4f8e-9427-dc323a725955)
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+
![image](https://github.com/DotNETWeekly-io/DotNetWeekly/assets/11272110/87f158b5-3a2f-4756-a042-9231fb659b9d)
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+
![image](https://github.com/DotNETWeekly-io/DotNetWeekly/assets/11272110/e992dcb8-916b-4e1e-8ff5-5d9543540295)
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+
![image](https://github.com/DotNETWeekly-io/DotNetWeekly/assets/11272110/0eab4318-7951-4a45-a2ca-32eeaf7978ee)
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+
![image](https://github.com/DotNETWeekly-io/DotNetWeekly/assets/11272110/9bbc04ef-f1a6-454c-9f4a-4a583292eaf9)
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+
![image](https://github.com/DotNETWeekly-io/DotNetWeekly/assets/11272110/2a2079b6-e091-4ac0-b92f-4c25a3c487f9)
308+
309+
`Rin` 是一个 `ASP.NET Core` 的开源插件,它可以记录和展示每个 `Web API` 请求和响应的细节,以及时间线和 Trace 日志。
310+
311+
![image](https://github.com/DotNETWeekly-io/DotNetWeekly/assets/11272110/e00e749e-feff-4521-8a87-e47923c2ef2e)
312+
313+
2、[AWS .NET Developer Guides](https://github.com/aws-samples/aws-net-guides)
314+
315+
![image](https://github.com/DotNETWeekly-io/DotNetWeekly/assets/11272110/0d2cc084-8ec3-44c8-901b-8837c7e23a76)
316+
317+
如果你想在 `AWS` 上开发和部署自己的 `.NET` 应用程序,那么这个开源项目可以参考一下,它包含了几乎大部分 `AWS` 的服务,比如通信,容器化,部署,监控,存储等等。
318+
319+
3、[OpenAI-dotnet](https://github.com/openai/openai-dotnet)
320+
321+
![image](https://github.com/DotNETWeekly-io/DotNetWeekly/assets/11272110/72547509-9a32-40b2-a986-1955db203e63)
322+
323+
OpenAI 出品的 `.NET` SDK, 支持各种模型。

0 commit comments

Comments
 (0)