场景:
假如一个集合中有一百万条数据,我们只想要前1000条。
错误代码示范
foreach (var c in GetCustomers(1000000))
{
if (c.Id < 1000)
{
Console.WriteLine($"ID:{c.Id},Name:{c.Name}");
}
else
{
break;
}
}
Console.Read();
static IEnumerable<Customer> GetCustomers(int count)
{
List<Customer> listcustomers = new List<Customer>();
for (int i = 0; i < count; i++)
{
var customer = new Customer
{
Id = i,
Name = $"Name{i}"
};
listcustomers.Add(customer);
}
return listcustomers;
}
这种代码有个很大的错误就是,我们明明只需要1000条数据,它却创建了一百万条数据,极大的浪费了内存。
正确示范,使用yield return
关键字。
foreach (var c in GetCustomersYield(1000000))
{
if (c.Id < 1000)
{
Console.WriteLine($"ID:{c.Id},Name:{c.Name}");
}
else
{
break;
}
}
Console.Read();
static IEnumerable<Customer> GetCustomersYield(int count)
{
for (int i = 0; i < count; i++)
{
yield return new Customer {
Id=i,
Name=$"Name{i}"
};
}
}
通过运行代码可以看到,GetCustomersYield(1000000)
不会立刻去创建实体,甚至代码不会直接运行GetCustomersYield
这个方法,他会先走循环,然后根据循环体需求,去创建数据。yield return
底层是对迭代器的实现。所以方法可以直接返回IEnumerable<Customer>
类型,除非数据必须使用,不然yield return
返回的对象是不会被立即创建的,有点懒加载的意思。实际上yield return
返回的并不是数据,而是数据的迭代。通过多次叠加,其实循环遍历的结果,就是迭代器的集合。这样使用yield return
关键字就只创建出来了1000条数据,大大的节省了内存开销。
接下来我们对 上面的2个代码 示例做下性能对比 。
Nuget 下载BenchmarkDotNet
跑分工具。
单独创建一个BenchmarkTest
类去跑
[MemoryDiagnoser]
public class BenchmarkTest
{
[Benchmark]
public void ProcessCustomer()
{
var customers = GetCustomers(1000000);
foreach (var c in customers)
{
if (c.Id < 1000)
{
Console.WriteLine($"ID:{c.Id},Name:{c.Name}");
}
else
{
break;
}
}
}
[Benchmark]
public void ProcessCustomerYield()
{
var customers = GetCustomersYield(1000000);
foreach (var c in customers)
{
if (c.Id < 1000)
{
Console.WriteLine($"ID:{c.Id},Name:{c.Name}");
}
else
{
break;
}
}
}
static IEnumerable<Customer> GetCustomers(int count)
{
List<Customer> listcustomers = new List<Customer>();
for (int i = 0; i < count; i++)
{
var customer = new Customer
{
Id = i,
Name = $"Name{i}"
};
listcustomers.Add(customer);
}
return listcustomers;
}
static IEnumerable<Customer> GetCustomersYield(int count)
{
for (int i = 0; i < count; i++)
{
yield return new Customer
{
Id = i,
Name = $"Name{i}"
};
}
}
}
切记类上和方法上要加上对应的特性。
主方法调用
static void Main(string[] args)
{
var sumery = BenchmarkRunner.Run<BenchmarkTest>();
}
Benchmark只能在Release
版本进行跑分,打开项目根目录。然后文件夹上 cmd
输入如下命令生成Release
dotnet build -c Release
拿到画线的地址,执行如下命令
dotnet D:\test\集合操作\集合操作\bin\Release\net5.0\集合操作.dll
等待跑分结果,如图:
C#课程欢迎留言或者私聊。