Skip to content

Latest commit

 

History

History
183 lines (119 loc) · 7.86 KB

episode-054.md

File metadata and controls

183 lines (119 loc) · 7.86 KB

.NET 每周分享第 54 期

卷首语

image

Hack News 上关于 Powershell讨论,每个人都有自己的看法和偏好。

文章推荐

1、和 Stephen Toub 学习 Linq

image

LINQC# 中非常要中的功能,其中 IEnunerable<T> 是重要的接口。我们都知道 C# 编译器为其中做了很多工作,这里 .NET 社区著名开发者 Stephen Toub 展示了徒手实现编译器完成的这个工作。

2、使用 Primary 构造函数重构 C# 代码

image

C# 12引入了 Primary Constructor 这个新的语法糖,这篇文章介绍了这个语法糖的使用

  1. 它是从 Record 类型启发而来
  2. 它会创建同名的的成员变量
  3. 如果使用使用 readonly 修饰成员变量,那么需要显示写出
  4. 如果有多个构造函数,那么其他的构造函数必须使用 this 调用 primary 构造函数

3、IO操作没有线程

image

C# 异步代码中,常常有人会有这样的一个疑问,是不是有一个线程在等待异步的完成,比如

private async void Button_Click(object sender, RoutedEventArgs e)
{
  byte[] data = ...
  await myDevice.WriteAsync(data, 0, data.Length);
}

WriteAsync 方法中,是不是有一个线程在调用这个方法。答案是否定的。没有任何一个线程调用这个方法,这个要从 CPU 的硬件底层来讲,当硬件开始一个 I/O 操作的时候,CPU 会继续处理其他事情,当 I/O 操作完成之后,会给 CPU 发出一个中断的信号,这时候内容的应用程序会将后续的操作作为一个 continuation 塞入某个线程的队列中。

4、ThreadPool

ThreadPool 是 .NET 异步并发编程中的重要的概念,这个官方文章详细介绍这个概念。首先 ThreadPoolbackgroud 线程,而且通过 pool 的方式,在完成任务后,线程还会放回 pool 中。 而且 ThreadPool 中的线程的数量是由 ThreadPool.GetMaxThreads 决定的,任务的数量是由内存大小决定。使用 ThreadPool 也非常简单,只需要调用 Task 或者 Task<Result> 相关方法就能将一个任务塞给线程池。

5、.NET 8 中的 ConfigureAwait

.NET 异步中, ConfigureAwait 方法通常用来只配置异步方法后续调用过程中的执行情况,默认为 true, 那么它会捕获当前线程的 Context,然后在异步操作完成后,在该线程上执行后续操作,反之则使用任何一个线程来处理后续的 continuation·。 在.NET 8` 中增加了新的参数,它是一个枚举类型

public enum ConfigureAwaitOptions
{
    None = 0x0,
    ContinueOnCapturedContext = 0x1,
    SuppressThrowing = 0x2,
    ForceYielding = 0x4,
}
  • None 相当于 ConfigureAwait(false), 表明不需要任何捕获之前 Context

  • ContinueOnCapturedContext

相当于 ConfigureAwait(true) 后续操作是在之前的 context 执行

  • SuppressThrowing

它会把所有的异常忽略掉

  • ForceYielding

当异步中遇到一个异步操作的时候,如果这个操作已经完成,则不会发生线程回退,但是 ForceYielding 会强制交出线程。

6、ValueTask 和 ValueTask

image

TaskTask<TResult> C# 异步编程中主要的组成部分,在.NET 2.0 之后,又引入了 ValueTaskValueTask<TResult> 类型,显而易见,它是一个值类型,主要是为了解决那些非异步操作的在内存消耗上的性能问题,当然在使用的时候还有一些注意点。

7、F# 学习之旅

F# 社区的 Scott Wlaschin 分享了自己 F# 学习之旅。

8、C# 中的设计模式

image

这篇文章详细介绍了使用 C# 实现常见的设计模式。

9、使用集合表达式重构代码

image

C# 12 中引入集合表达式,它可以辅助我们写出更加简洁的代码。

  • 初始化
var numbers1 = new int[3] { 1, 2, 3 };
var numbers2 = new int[] { 1, 2, 3 };
var numbers3 = new[] { 1, 2, 3 };
int[] numbers4 = { 1, 2, 3 };

注意最后一个不能使用 var 变量。

  • 空集合
int[] emptyCollection = [];

通过这种方式,编译器可以生成最好的的空集合表达方式。

  • 展开

可以使用 .. 命令,将多个集合凭借起来

int[] onetwothree = [1, 2, 3];
int[] fourfivesix = [4, 5, 6];

int[] all = [.. fourfivesix, 100, .. onetwothree];

这里可以优化我们的代码,比如 ToList

static List<Query> QueryStringToList(string queryString) => [.. from queryPart in queryString.Split('&')
                                                              let keyValue = queryPart.Split('=')
                                                              where keyValue.Length is 2
                                                              select new Query(keyValue[0], keyValue[1])];
  • 性能区别

下面两种集合初始化的方法有什么区别?

List<int> someList1 = new() { 1, 2, 3 };
List<int> someList2 = [1, 2, 3];

如果我们查看编译器生成的代码,可以发现其中的不同

List<int> list = new List<int>();
 list.Add(1);
list.Add(2);
list.Add(3);
List<int> list2 = list;
List<int> list3 = new List<int>();

CollectionsMarshal.SetCount(list3, 3);
 Span<int> span = CollectionsMarshal.AsSpan(list3);
 int num = 0;
span[num] = 1;
num++;
 span[num] = 2;
num++;
span[num] = 3;
num++;
List<int> list4 = list3;

第一种方式首先创建了一个 List 对象,然后将元素依次加入其中;第二种方式是创建 List 对象,然后修改内部集合数据大小,然后在相应的位置修改元素值。当数据量大的时候,第二种方式性能更加好。

开源项目

1、roslynpad

image

使用 Roslyn 和 AvalonEdit 开发的开源 C# 编辑器,可以简单替换 LinqPad

2、.NET Benchmark 集锦

.NET 在每个版本中都强调了在性能上的优化,相同结果的不同代码在性能上也有不同的表现。这个网站收集了很多社区提交的性能比较的案例,大家可以在浏览,借鉴和学习,当然也可以通过提交自己的 Benchmark 的例子丰富这个社区。