这是 Reddit 上的一个的讨论,提问者是一位在 .NET Framework
上工作超过 9 年的开发者。当他尝试投递其他更加现代的 .NET
或者 Azure 相关的开发工作的时候,在面试结束后,并没有得到后续的回复。他的问题是该如何从传统的 .NET
职位跳槽到新的 .NET
职位。
社区给了很多建议
- 假装拥有现代
.NET
的技能 - 切换语言并不是一件很罕见的事情,我们应该适应它
- 可以在工作中,将小的组件逐步迁移到现代的
.NET
- ...
Reddit
上有一个 Blazor
流行度的讨论,总体而言,有不少人在生产环境中使用 Blazor
,也有一些人在尝试之后就放弃了使用。
Refit
是 .NET
中比较著名的开源库。文章作者分享了自己在使用 Refit
的一些最佳实践。主要是不通过直接定义的接口来使用它,而是将它封装成另外一个服务,这样可以做额外的工作,比如认证,授权和异常处理。
public interface IMyHttpApiClient
{
[Get("/users/{username}/repos")]
Task<List<Repository>> GetUserRepositoriesAsync(string username);
}
// Creating a Refit client
IMyHttpApiClient gitHubApi = RestService.For<IMyHttpApiClient >("https://api.github.com");
public class MyApiProvider(IMyHttpApiClient Client)
{
public Task<UserResponseItem> GetUser(string user)
{
return Execute(() => Client.GetUser(user));
}
}
Builder Pattern
是一种设计模式,通常有下面的特点
- 创造设计模式
- 简化构建复杂对象
- 简洁,更加直观的代码
- 复杂度增加,许多新类
在 ASP.NET Core
中依赖注入中包含了很多这样的设计,这里一个简单的 C#
例子来展示这个模式实现
var order = OrderBuilder.Empty.SetName("Order 1")
.WithNumber(1)
.ShipTo(s => s.City("São Paulo").ZipCode("01310-100"))
.Build();
public class Order {
public string Name { get; set; }
public int Number{ get; set; }
public Street Street { get; set; }
}
public class Street {
public string City { get; set; }
public string ZipCode { get; set; }
}
public class OrderBuilder {
public static OrderBuilder Empty => new OrderBuilder();
private OrderBuilder() {}
private string _name;
private int _number;
private StreetBuilder _streetBuilder = StreetBuilder.Empty;
public OrderBuilder SetName(string name) {
_name = name;
return this;
}
public OrderBuilder WithNumber(int number) {
_number = number;
return this;
}
public OrderBuilder ShipTo(Action<StreetBuilder> buildStreet) {
buildStreet(_streetBuilder);
return this;
}
public Order Build() {
return new Order{Name = _name, Number = _number,
Street = _streetBuilder.Build()};
}
}
public class StreetBuilder {
public static StreetBuilder Empty => new StreetBuilder();
private StreetBuilder() {}
private string _city;
private string _zipCode;
public StreetBuilder City(string city) {
_city = city;
return this;
}
public StreetBuilder ZipCode(string zipCode) {
_zipCode = zipCode;
return this;
}
public Street Build() { return new Street{City = _city, ZipCode = _zipCode}; }
}
这里的 Order
是目标的对象,但是我们增加了 OrderBuilder
类,它的 SetName
和 WithNumber
两个方法是保存相关属性,而 Street
是另外个一个对象,所以我们有构建了 StreetBuilder
类,在 OrderBuilder
中使用接受一个 Action<StreetBuilder>
委托来创建这个属性,最后的 Build
方法把记录的数据和委托构造出一个 Order
对象。
3、不可变字典比较
在 C#
中有 ReadOnlyDictionary
, ImmutableDictionary
和 ForzenDictionary
三种类型,那么它们在功能和性能上有什么区别呢?
- 构造
[Benchmark]
public void Create_ReadOnlyDictionary() {
var dictionnary = new Dictionary<string, Goat>();
for (int i = 0; i < 1000000; i++) {
dictionnary.Add(i.ToString(), new Goat{Name = i.ToString()});
}
var result = new ReadOnlyDictionary<string, Goat>(dictionnary);
}
[Benchmark]
public void Create_ImmutableDictionary() {
var dictionnary = new Dictionary<string, Goat>();
for (int i = 0; i < 1000000; i++) {
dictionnary.Add(i.ToString(), new Goat{Name = i.ToString()});
}
var result = dictionnary.ToImmutableDictionary();
}
[Benchmark]
public void Create_FrozenDictionary() {
var dictionnary = new Dictionary<string, Goat>();
for (int i = 0; i < 1000000; i++) {
dictionnary.Add(i.ToString(), new Goat{Name = i.ToString()});
}
var result = dictionnary.ToFrozenDictionary();
}
Benchmark 的结果如下
Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated |
---|---|---|---|---|---|---|---|
Create_ReadOnlyDictionary | 967.4 us | 742.6 us | 40.70 us | 220.7031 | 220.7031 | 220.7031 | 1.72 MB |
Create_ImmutableDictionary | 6,556.5 us | 27,352.5 us | 1,499.28 us | 218.7500 | 218.7500 | 218.7500 | 2.33 MB |
Create_FrozenDictionary | 2,985.9 us | 7,518.3 us | 412.10 us | 359.3750 | 359.3750 | 359.3750 | 2.6 MB |
可以看出 ReadOnlyDictionary
创建最快,FrozenDictionary
次之,最后是 ImmutableDictionary
最慢。 原因是什么呢? ReadOnlyDictionary
是复用了底层 Dictionary
结构,只不过丢弃了修改的操作,比如 Add
等,所以如果还能访问底层的 Dictionary
, 也是有机会去修改 ReadOnlyDictionary
; ImmutableDictionary
是 Immutable
的实现,当你在修改这个字典的时候,它会创建一个新的字典,而保持原有的不变;
- 读取
[Benchmark]
public void TryGetValue_ReadOnlyDictionary() {
for (int i = 0; i < 1000000; i++) {
var index = Random.Shared.Next(0, N);
_readOnlyDictionary.TryGetValue(index.ToString(), out var value);
}
}
[Benchmark]
public void TryGetValue_ImmutableDictionary() {
for (int i = 0; i < 1000000; i++) {
var index = Random.Shared.Next(0, N);
_immutableDictionary.TryGetValue(index.ToString(), out var value);
}
}
[Benchmark]
public void TryGetValue_FrozenDictionary() {
for (int i = 0; i < 1000000; i++) {
var index = Random.Shared.Next(0, N);
_frozenDictionary.TryGetValue(index.ToString(), out var value);
}
}
Benchmark 结果如下
Method | Mean | Error | StdDev | Gen0 | Allocated |
---|---|---|---|---|---|
TryGetValue_ReadOnlyDictionary | 676.7 us | 502.6 us | 27.55 us | 31.2500 | 303.14 KB |
TryGetValue_ImmutableDictionary | 1,514.5 us | 845.4 us | 46.34 us | 31.2500 | 303.12 KB |
TryGetValue_FrozenDictionary | 391.3 us | 1,828.4 us | 100.22 us | 32.7148 | 303.13 KB |
可以看出 ReadOnlyDictionary
创建最快,FrozenDictionary
次之,最后是 ImmutableDictionary
最慢。之前我们讨论 ImmutableDictionary
是不可变的,可以在多线程中使用。但是 FrozenDictionary
也是不可变的字段,但是它在读取的数据的性能上比 ImmutableDicionary
好很多。
所以,我们可以得到结论,在多线程环境中, ReadOnlyDictionary
并不是好的类型,ImmutableDictionary
可以在多线程写操作中发挥作用,FrozenDictionary
在多线程读操作中发挥作用。
这是 C# 新功能介绍的一系列文章,主要介绍了 Alias Any Type
的功能。不同于之前的功能介绍,这次作者按照一个具体的 demo 展示如何使用这个功能,大部分内容在 GlobalUsing.cs
文件中
// Ensures that all types within these namespaces are globally available.
global using Alias.AnyType;
global using Alias.AnyType.Extensions;
global using Alias.AnyType.ResponseModels;
// Expose all static members of math.
global using static System.Math;
// Alias a coordinates object.
global using Coordinates = (double Latitude, double Longitude);
// Alias representation of degrees-minutes-second (DMS).
global using DMS = (int Degree, int Minute, double Second);
// Alias representation of various distances in different units of measure.
global using Distance = (double Meters, double Kilometers, double Miles);
// Alias a stream of coordinates represented as an async enumerable.
global using CoordinateStream = System.Collections.Generic.IAsyncEnumerable<
Alias.AnyType.CoordinateGeoCodePair>;
// Alias the CTS, making it simply "Signal".
global using Signal = System.Threading.CancellationTokenSource;
global using Alias.AnyType
可以导入这个namespace
下所有成员global using static System.Match
可以导出这个命令空间下的静态成员global using DMS = (int Degree, int Minute, double Second)
将一个Tuple
类型使用DMS
别名global using Signal = System.Threading.CancellationTokenSource
是将一个系统类型CancellationTokenSource
使用Signal
别名
Primary Constructor
是 C#
12 引入的新的语法糖,其中的好和坏作者都做出的比较
优势
- 基础字段的初始化
- 简化单元测试类的初始化
- MVC controller 中的依赖注入
劣势
- 字段和参数冲突
- 没有办法标记
readonly
Struct
类型没法控制内存分布- 命名规则冲突
在 C# 异常处理中,通常我们只会关心 Message
, Stack Trace
等等内容,那么 HResult
这段字段代表什么意思呢?
首先 HResult
是 Handle to result
的简写,它是标准的在不同组件之间交流的错误信息的方式,它是一个 32 位的整型数据,包含了三个内容
- Severity
- Facility
- Code
try
{
int a = 1;
int b = 0;
int c = a / b;
}
catch (System.Exception e)
{
var isFailure = (e.HResult & 0x80000000) != 0; // isFailure is true
var facility = (e.HResult & 0x7FFF0000) >> 16; // facility is 2
var code = (e.HResult & 0xFFFF); // code is 18
System.Console.WriteLine($"isFailure: {isFailure}, facility: {facility}, code: {code}");
}
你也可以去 www.hresult.info 这个网站查询详细信息
1、Rin
Rin
是一个 ASP.NET Core
的开源插件,它可以记录和展示每个 Web API
请求和响应的细节,以及时间线和 Trace 日志。
如果你想在 AWS
上开发和部署自己的 .NET
应用程序,那么这个开源项目可以参考一下,它包含了几乎大部分 AWS
的服务,比如通信,容器化,部署,监控,存储等等。
OpenAI 出品的 .NET
SDK, 支持各种模型。