Linq 中默认值的平均扩展方法

Posted

技术标签:

【中文标题】Linq 中默认值的平均扩展方法【英文标题】:Average extension method in Linq for default value 【发布时间】:2011-07-24 22:31:29 【问题描述】:

有人知道如何设置平均值的默认值吗?我有这样的台词……

dbPlugins = (from p in dbPlugins
                select new  Plugin = p, AvgScore = p.DbVersions.Average(x => x.DbRatings.Average(y => y.Score)) )
            .OrderByDescending(x => x.AvgScore)
            .Select(x => x.Plugin).ToList();

这会引发错误,因为我还没有评级。如果我没有,我希望平均值默认为 0。我在想这应该是一种扩展方法,我可以在其中指定默认值应该是什么。

【问题讨论】:

【参考方案1】:

我的建议是创建一个可重复使用的解决方案,而不仅仅是针对这个问题的解决方案。

制作一个扩展方法AverageOrDefault,类似于FirstOrDefault。见extension methods demystified

public static class MyEnumerableExtensions

    public static double AverageOrDefault(this IEnumerable<int> source)
    
        // TODO: decide what to do if source equals null: exception or return default?
        if (source.Any())
            return source.Average();
        else
            return default(int);
    

Enumerable.Average 有 9 个重载,因此您需要为 double、int?、decimal 等创建一个 AverageOrDefault。它们看起来都相似。

用法:

// Get the average order total or default per customer
var averageOrderTotalPerCustomer = myDbContext.Customers
    .GroupJoin(myDbContext.Orders,
    customer => customer.Id,
    order => order.CustomerId,
    (customer, ordersOfThisCustomer) => new
    
        Id = customer.Id,
        Name  = customer.Name,
        AverageOrder = ordersOfThisCustomer.AverageOrDefault(),
    );

【讨论】:

【参考方案2】:

有:DefaultIfEmpty

我不确定您的 DbVersionsDbRatings 是什么,以及哪个集合恰好有零项,但这就​​是想法:

var emptyCollection = new List<int>();
var average = emptyCollection.DefaultIfEmpty(0).Average();

更新:(重复以下 cmets 中所说的以增加可见性)

如果您发现自己需要在类类型的集合上使用DefaultIfEmpty,请记住您可以将 LINQ 查询更改为 before 聚合。例如:

class Item

    public int Value  get; set; 


var list = new List<Item>();
var avg = list.Average(item => item.Value);

如果您不想/不能构造一个默认的ItemValue 等于0,您可以先投影到ints 的集合,然后然后提供一个默认:

var avg = list.Select(item => item.Value).DefaultIfEmpty(0).Average();

【讨论】:

我认为这不是一个好主意。假设你有一个List&lt;A&gt;,而A 是一个具有int 属性Num 的类。您需要编写类似listofA.DefaultItEmpty(defaultA).Average(a =&gt; a.Num) 的代码。你需要在这里构造一个defaultA @DannyChen:不正确:listofA.Select(a =&gt; a.Num).DefaultIfEmpty(0).Average(). @Jon:但你现在又回到了起点,x.DbRatings 在这里可以为空。 p.DbVersions.Select(x =&gt; x.DbRatings).DefaultIfEmpty(defaultRatings),需要指定defaultRatings,不是简单的数字。 @DannyChen: 为什么x.DbRatings 可以成为null?我当然希望它是一个空的 IQueryable&lt;DbRating&gt; 而不是 null @DannyChen:InvalidOperationException。请阅读文档:msdn.microsoft.com/en-us/library/bb354760.aspx【参考方案3】:

我认为没有办法选择默认值,但是这个查询怎么样

dbPlugins = (from p in dbPlugins
             select new  
                Plugin = p, AvgScore = 
                    p.DbVersions.Any(x => x.DbRatings) ?
                        p.DbVersions.Average(x => x.DbRatings.Average(y => y.Score)) : 0 )
             .OrderByDescending(x => x.AvgScore)
             .Select(x => x.Plugin).ToList();

与您的基本相同,但我们先询问是否有任何评分,然后再进行平均。如果不是,我们返回 0。

【讨论】:

以上是关于Linq 中默认值的平均扩展方法的主要内容,如果未能解决你的问题,请参考以下文章

Linq 中 Enumerable.Zip 扩展方法有啥用?

扩展方法使 LINQ 中断

扩展方法和Enumerable

“折叠”LINQ 扩展方法在哪里?

等效于链式 LINQ 扩展方法调用中的 'let' 关键字的代码

等效于链式 LINQ 扩展方法调用中的 'let' 关键字的代码