IEnumerable<Person> GetPersons()
{
var lines = File.ReadAllLines("./Person.csv");
foreach (var line in lines)
{
var segments = line.Split(',');
yield return new Person(segments[0], int.Parse(segments[1]));
}
}
var persons = GetPersons();
var cnt = persons.Count();
Console.WriteLine($"There are {cnt} persons");
foreach (var person in persons)
{
Console.WriteLine(person);
}
record Person(string Name, int Age);
对于上述代码,GetPersons
方法会调用几次呢?直觉告诉我们只会执行一次,但是并非如此,在
Count()
foreach
两个方法中都调用了 GetPerson
的实现,也就是执行了两边,为什么会这样呢?其实编译器会将 GetPersons
方法转换成下面的实现
private IEnumerable<Person> GetPersons()
{
return new <GetPersons>d__1(-2);
}
而 <GetPersons>d__1
类型为定义
private sealed class <GetPersons>d__1 : IEnumerable<Person>
{
private int <>1__state;
private Person <>2__current;
private int <>l__initialThreadId;
Person IEnumerator<Person>.Current
{
get { return <>2__current; }
}
public <GetPersons>d__1(int <>1__state)
{
this.<>1__state = <>1__state;
<>l__initialThreadId = Environment.CurrentManagedThreadId;
}
private bool MoveNext()
{
int num = <>1__state;
if (num != 0)
{
if (num != 1)
{
return false;
}
<>1__state = -1;
return false;
}
<>1__state = -1;
<>2__current = new Person("fenga", 10);
<>1__state = 1;
return true;
}
IEnumerator<Person> IEnumerable<Person>.GetEnumerator()
{
if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
{
<>1__state = 0;
return this;
}
return new <GetPersons>d__1(0);
}
}
而 Work
方法编译的结果如下
public void Work()
{
IEnumerable<Person> persons = GetPersons();
Console.WriteLine(Enumerable.Count(persons));
IEnumerator<Person> enumerator = persons.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}
}
finally
{
if (enumerator != null)
{
enumerator.Dispose();
}
}
}
在 Count()
方法中,调用了 Eunerable.Count()
方法
int count = 0;
using (IEnumerator<TSource> e = source.GetEnumerator())
{
checked
{
while (e.MoveNext)
{
count++;
}
}
}
所以重点来到了生成的 GetEnumerator
方法,
IEnumerator<Person> IEnumerable<Person>.GetEnumerator()
{
if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
{
<>1__state = 0;
return this;
}
return new <GetPersons>d__1(0);
}
可以清楚的知道,在第二次迭代的时候,重新创建了一个 <GetPersons>d_1
对象实例,在其中的 MoveNext
方法中,又重新执行对象的创建。
那么怎么解决这个问题呢?答案是将其转换成具体的类型,比如 List<Person>
对象。
.NET 7
即将要在两个月后发布,最近 Stephen Toub
发表了一篇文章,着重介绍了 .NET 7
在性能提升上做出的努力。
.NET 6 对文件 I/O 操作上有很大的提升,这篇文档带你详细分析这方面的提升。
在 .NET 诞生之初,它就对 Container
做了大量的支持。虽然我们可以通过 Dockerfile
来配置,但是新的 .NET
SDK 已经内置了对它的支持,比如在 csproj
文件中配置相关信息。
这篇文章列出了一个 .NET
开发者必备的工具箱。
在 .NET 6
之前,在 C#
中如下的的这些计时器
System.Threading.Timer
System.Timers.Timer
System.Windows.Forms.Timer
System.Web.UI.Timer
System.Windows.Threading.DispatcherTimer
一般我们用的最多的是 System.Threading.Timer
类型,一个典型的用法如下
Timer timer = new Timer(new TimeCallback(DoWork), null, 1000, 100);
static void DoWork(object state)
{
// ...
}
DoWork
在定时器每次 Tick
的时候执行,但是使用 Callback
方式,这样做增加的复杂度和资源泄露的问题。 在 .NET 6
中引入了 PeriodicTimer
类型,使用方式也非常直接
var timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
while (await timer.WaitForNextTickAsync())
{
//Business logic
}
这种使用方式的好处有:
- 没有使用回调的方式
- 支持
CancellationToken
方式取消定时器 - 支持异步调用
async/await
是 C#
中的异步的实现,我们也知道编译器在后台完成了大量的工作。那么这篇文章就逐行介绍一个异步方法背后生成的代码。
Linq 很难懂?这篇给文章通过手绘的方式展示了各个 Linq 的功能,简单而明了。
这是一系列 .NET GC
的教程,详细介绍了 .NET
GC 的全部内容,包含了
- Mark Phase
- Concurrent Mark Phase
- Plan Phase
- Sweep Phase
- Compact Phase
- Allocation
- Generations
- GC Roots
1、ILSpy
ILSpy 是一个开源的 .NET 程序集浏览器和反编译工具,该工具支持 PDB,ReadyToRun, Metadata 还有其他类型的文件信息,同时该工具是跨平台的。
2、ML.NET
ML.NET 允许开发人员在其 .NET 应用程序中轻松生成、训练、部署和使用自定义模型,而无需事先具备开发机器学习模型的专业知识或使用其他编程语言(如 Python 或 R)的经验。该框架提供从文件和数据库加载数据,支持数据转换,并包括许多 ML 算法。