Average extension method in Linq for default value

久未见 提交于 2020-05-14 14:40:29

问题


Anyone know how I can set a default value for an average? I have a line like this...

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();

which throws an error becase I have no ratings yet. If I have none I want the average to default to 0. I was thinking this should be an extension method where I could specify what the default value should be.


回答1:


There is: DefaultIfEmpty.

I 'm not sure about what your DbVersions and DbRatings are and which collection exactly has zero items, but this is the idea:

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

Update: (repeating what's said in the comments below to increase visibility)

If you find yourself needing to use DefaultIfEmpty on a collection of class type, remember that you can change the LINQ query to project before aggregating. For example:

class Item
{
    public int Value { get; set; }
}

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

If you don't want to/can not construct a default Item with Value equal to 0, you can project to a collection of ints first and then supply a default:

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



回答2:


I don't think there's a way to select default, but how about this query

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();

Essentially the same as yours, but we first ask if there are any ratings before averaging them. If not, we return 0.




回答3:


My advice would to create a reusable solution instead of a solution for this problem only.

Make an extension method AverageOrDefault, similar to FirstOrDefault. See 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);
    }
}

There are 9 overloads of Enumerable.Average, so you'll need to create an AverageOrDefault for double, int?, decimal, etc. They all look similar.

Usage:

// 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(),
    });


来源:https://stackoverflow.com/questions/5467114/average-extension-method-in-linq-for-default-value

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!