避免循环生成可空集合,提高性能

时间:2016-06-04 14:40:46

标签: c# performance linq

我一直在使用秒表,看起来下面的查询在性能方面非常昂贵,即使我已经在下面找到了基于各种读数的最佳选择(使用for更改foreach循环,使用数组)而不是集合,使用匿名类型不从DB获取整个表)。有没有办法让它更快?我需要填写价格数组,这需要可以为空。我不确定我是否遗漏了某些东西?

public float?[] getPricesOfGivenProducts(string[] lookupProducts)
{
    var idsAndPrices = from r in myReadings select 
                       new { ProductId = r.ProductId, Price = r.Price };

    float?[] prices = new float?[lookupProducts.Length];
    for(int i=0;i<lookupProducts.Length;i++)
    {
        string id = lookupProducts[i];
        if (idsAndPrices.Any(r => r.ProductId == id))
        {
            prices[i] = idsAndPrices.Where(p => p.ProductId == id)
            .Select(a=>a.Price).FirstOrDefault();
        }
        else
        {
            prices[i] = null;
        }
    }
    return prices;
}

5 个答案:

答案 0 :(得分:4)

每次拨打idsAndPrices.Any(r => r.ProductId == id)时,您都可能正在访问数据库,因为您还没有实现结果(.ToList()会稍微修复一下)。这可能是表现不佳的主要原因。但是,只需将其全部加载到内存中仍然意味着您每次都会在列表中搜索productID(实际上每个产品两次)。

在您尝试进行查找时使用Dictionary

public float?[] getPricesOfGivenProducts(string[] lookupProducts)
{
    var idsAndPrices = myReadings.ToDictionary(r => r.ProductId, r => r.Price);

    float?[] prices = new float?[lookupProducts.Length];

    for (int i = 0; i < lookupProducts.Length; i++)
    {
        string id = lookupProducts[i];
        if (idsAndPrices.ContainsKey(id))
        {
            prices[i] = idsAndPrices[id];
        }
        else
        {
            prices[i] = null;
        }
    }
    return prices;
}

为了进一步改进,我们可以确定我们关心在阵列中传递给我们的产品。所以我们不要加载整个数据库:

var idsAndPrices = myReadings
                       .Where(r => lookupProducts.Contains(r.ProductId))
                       .ToDictionary(r => r.ProductId, r => r.Price);

现在,如果我们无法找到产品,我们可能希望避免返回零价格。场景。也许产品ID的有效性应该在别处处理。在这种情况下,我们可以使方法更简单(我们也不必依赖于按顺序排列数组):

public Dictionary<string, float> getPricesOfGivenProducts(string[] lookupProducts)
{
    return myReadings
               .Where(r => lookupProducts.Contains(r.ProductId))
               .ToDictionary(r => r.ProductId, r => r.Price);
}

与绩效无关的说明,您应该use decimal for money

答案 1 :(得分:2)

假设idsAndPricesIEnumerable<T>,您应该进行初始化:

var idsAndPrices = (from r in myReadings select 
                   new { ProductId = r.ProductId, Price = r.Price })
                   .ToList();

可能会拨打:

idsAndPrices.Any(r => r.ProductId == id)

idsAndPrices.Where(p => p.ProductId == id)

..导致IEnumerable<T>每次被调用时都会被评估。

答案 2 :(得分:2)

基于

  

使用匿名类型不从DB中获取整个表

我认为myReadings是数据库表和

var idsAndPrices = 
    from r in myReadings
    select new { ProductId = r.ProductId, Price = r.Price };

是数据库查询。

你的实现远非最优(我宁愿说非常低效),因为上述查询每{/ 1}}个数组{/ 1}}和{{}每个元素执行两次1}}陈述。

我看到的最佳方式是尽可能多地过滤数据库查询,然后使用最有效的LINQ to Objects方法关联内存序列中的两个 - lookupProducts,在您的情况下left outer join

idsAndPrices.Any(...)

答案 3 :(得分:0)

AnyFirstOrDefault是O(n)并且是多余的。只需删除All电话,您就可以加快50%的速度。 FirstOrDefault会返回null,因此请使用它来获取产品对象(删除Select)。如果你想真正加快速度,你应该在设置prices[p.ProductId] != null之前浏览产品并检查是否prices[p.ProductId] = p.Price

答案 4 :(得分:0)

那里有一些额外的代码

var idsAndPrices = (from r in myReadings select 
               new { ProductId = r.ProductId, Price = r.Price })
               .ToList();
for(int i=0;i<lookupProducts.Length;i++)
{
    string id = lookupProducts[i];
    prices[i] = idsAndPrices.FirstOrDefault(p => p.ProductId == id);
}

更好

Dictionary<Int, Float?> dp = new Dictionary<Int, Float?>();
foreach(var reading in myReadings) 
    dp.add(r.ProductId, r.Price);
for(int i=0;i<lookupProducts.Length;i++)
{
    string id = lookupProducts[i];
    if(dp.Contains(id)
        prices[i] = dp[id];
    else 
        prices[i] = null;
}