C#中 使用yield return 优化大数组或集合的访问

概要

我们在开发过程中,经常需要在一个很大的数组或集合中搜索元素,以满足业务需求。

本文主要介绍通过使用yield return的方式,避免将大量数据全部加载进入内存,再进行处理。从而提高程序的性能。

设计和实现

基本业务场景,我们需要在10000台ATM的数据中找前100台品牌是BrandA的ATM机的数据。

我们不再使用传统的方式,将10000台ATM机的数据全部载入内容,再进行过滤查找。

我们通过yield return方式,只返回一个迭代器,代码如下:

本例中,存在BrandA和BrandB两个品牌,在生成ATM的L集合序列时候,每次都是随机生成ATM机的品牌。

public IEnumerable<ATM> getATMListYield(){
    List<ATM> atms = new List<ATM>();
    int count = 0;
    for(var i=0; i< 10000; ++i){
        yield return new ATM (){
            Id = i,
            Name = "Atm" + i,
            Brand =  getBrand()
        } ;
    }
    yield break;
}
private string getBrand(){
    Random rd = new Random();
    int count = rd.Next(100);
    if (count >= 50) return "BrandA";
    return "BrandB";
}

调用getATMListYield,进行过滤,找到前100个BrandA的ATM机。完整代码,请参考附录。

 public void runGetList(){
     DataProvider dp = new DataProvider();
     var lists = dp.getATMList();
     var count = 0;
     foreach(var atm in lists){
         if(atm.Brand == "BrandA") {
             Console.WriteLine(atm.Name );
             ++ count;
         }
         if (count == 100){
             break;
         }
     }
 }

在foreach循环中,每次访问ATM的集合,只将集合中的一个元素载入内存,进行过滤和比较,当找到100个BrandA的元素,程序停止,不再载入ATM数组的其它元素。

载入全部ATM数据,再进行过滤的代码请见附录。

我们使用Benchmark对两种实现的性能进行测试,测试结果如下:

C#中 使用yield return 优化大数组或集合的访问_第1张图片
从测试结果中,可以看出,使用yield return方式,运行时间几乎减少了一半。

由于不需要将全部ATM数据载入内容,yield return方式的内存使用量,仅仅相当于传统方法的2%左右。

附录

Programs.cs

using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Net.Mail;
using System.ComponentModel.Design.Serialization;
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Collections.Generic;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
namespace IQueryableIEnumerable
{
    [MemoryDiagnoser]
    public class Programs
    {
        [Benchmark]
        public void runGetList(){
            DataProvider dp =new DataProvider();
            var lists = dp.getATMList();
            var count = 0;
            foreach(var atm in lists){
                if(atm.Brand == "BrandA") {
                    Console.WriteLine(atm.Name );
                    ++ count;
                }
                if (count == 100){
                    break;
                }
            }
        }
        [Benchmark]
        public void runGetListByYield(){
            DataProvider dp =new DataProvider();
            var lists = dp.getATMListYield();
            int count = 0;
            foreach(var atm in lists){
                if(atm.Brand == "BrandA") {
                    Console.WriteLine(atm.Name );
                    ++ count;
                }
                if (count == 100){
                    break;
                }
            }
        }
        public static void Main(string[] args)
        {
             var summary = BenchmarkRunner.Run<Programs>();      
        }

      
    }
}

DataProvider.cs

using System;
using System.Linq;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;

namespace IQueryableIEnumerable
{
    public class DataProvider {
        public IEnumerable<ATM> getATMList(){
            List<ATM> atms = new List<ATM>();
            for(var i=0; i< 10000; ++i){
                atms.Add(new ATM (){
                    Id = i,
                    Name = "Atm" + i,
                    Brand =  getBrand()
                });
            }
            return atms;
        }
        public IEnumerable<ATM> getATMListYield(){
            List<ATM> atms = new List<ATM>();
            int count = 0;
            for(var i=0; i< 10000; ++i){
                yield return new ATM (){
                    Id = i,
                    Name = "Atm" + i,
                    Brand =  getBrand()
                } ;
            }
            yield break;
        }
        private string getBrand(){
            Random rd = new Random();
            int count = rd.Next(100);
            if (count >= 50) return "BrandA";
            return "BrandB";
        }
    }
}

ATM.cs

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace IQueryableIEnumerable
{
    public class ATM {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Brand {get;set;}      
    }
}

你可能感兴趣的:(C#基础,.Net,.Net,Core,c#,开发语言)