LINQ中使用Min或Max时如何处理空值?

44

我有以下的Linq查询:

result.Partials.Where(o => o.IsPositive).Min(o => o.Result)

result.Partials.Where(o => o.IsPositive)不包含元素时,我会收到一个异常。除了将操作拆分为两部分并检查null之外,是否有更优雅的处理方式?我的类中充满了像这样的操作。

编辑:该问题与LINQ to Objects有关。

我得到的异常如下(翻译后说:“序列为空”):

enter image description here


你得到了什么异常?根据我的经验,如果“Partials”为空,你应该得到0。 - Kendall Frey
你的堆栈跟踪中调用了 Min() 而不是 Sum()result 是什么? - Jon
抱歉,我的错误,操作应该是最小值和最大值而不是求和。再次道歉。正在编辑问题。 - Ignacio Soler Garcia
你确定出现异常的是那一行代码吗?异常是由Min()方法而不是 Where()Sum()引起的。 - Kendall Frey
5个回答

78

计算Min的简要总结

- 没有中介(例外!)

   var min = result.Partials.Where(o => o.IsPositive).Min(o => o.Result);

这是您的情况:如果没有匹配的元素,那么 Min 调用将引发一个异常 (InvalidOperationException)。

- 使用 DefaultIfEmpty() 仍然有问题

 var min = result.Partials.Where(o => o.IsPositive)
                          .Select(o => o.Result)
                          .DefaultIfEmpty()
                          .Min();

DefaultIfEmpty 方法将在列表中没有元素时创建一个只包含 0 元素的枚举。如何确定 0 是 Min,还是代表一个空列表?

- 可空值;更好的解决方案

   var min = result.Partials.Where(o => o.IsPositive)
                            .Min(o => (decimal?)o.Result);

这里的 Min 可能是空值(因为等同于 default(decimal?)),或者是实际找到的最小值。

所以,这个结果的使用者将会知道:

  1. 当结果为 null 时,列表没有任何元素
  2. 当结果是一个十进制数值时,列表有一些元素,并且这些元素的Min是返回值。

然而,当这不重要时,可以调用 min.GetValueOrDefault(0)


3
如果您的属性可为空且源集合可以包含null值,那么获取到null结果可能有两种情况:要么是集合没有元素,要么是给定属性的所有值都是null。请注意,这里的翻译尽力保持了原文的意思和语气,并使用通俗易懂的方式表达。 - Rudey

10
你可以使用 DefaultIfEmpty 方法来确保集合至少有一个项:
result.Partials.Where(o => o.IsPositive).Select(o => o.Result).DefaultIfEmpty().Min();

如果默认值为null怎么办? - Ritch Melton
@RitchMelton:我相信Min会返回null。 - Kendall Frey
我非常确定,如果使用空元素调用o.Result会导致抛出异常。 - Ritch Melton
@Ritch:是的,当然。我正在阅读decimal?的文档,而不是通用情况。我会更新答案。 - Kendall Frey
太笨重了。我使用Adrian Iftode建议的相同方法 - 将选定的字段转换为可空类型。 - maxlego

7

如果序列为空,则无法使用Min(或Max)。 如果出现这种情况,您在定义result时可能有其他问题。 否则,您应该检查序列是否为空并适当处理,例如:

var query = result.Partials.Where(o => o.IsPositve);
min = query.Any() ? query.Min(o => o.Result) : 0; // insert a different "default" value of your choice...    

1

在LINQ中表达它的另一种方式是使用Aggregate

var min = result.Partials
    .Where(o => o.IsPositive)
    .Select(o => o.Result)
    .Aggregate(0, Math.Min); // Or any other value which should be returned for empty list

1

由于LINQ缺少像MinOrDefault()MaxOrDefault()这样的方法,您可以自己创建它们:

public static class LinqExtensions
{
    public static TProp MinOrDefault<TItem, TProp>(this IEnumerable<TItem> This, Func<TItem, TProp> selector)
    {
        if (This.Count() > 0)
        {
            return This.Min(selector);
        }
        else
        {
            return default(TProp);
        }
    }
}

因此,如果集合有值,则计算Min(),否则您将获得属性类型的默认值。
使用示例:
public class Model
{
    public int Result { get; set; }
}

// ...

public void SomeMethod()
{
    Console.WriteLine("Hello World");
    var filledList = new List<Model>
    {
        new Model { Result = 10 },
        new Model { Result = 9 },
    };
    var emptyList = new List<Model>();
    var minFromFilledList = filledList.MinOrDefault(o => o.Result)); // 9
    var minFromEmptyList = emptyList.MinOrDefault(o => o.Result)); // 0
}

注意1:你不需要检查This参数是否为空:调用的Count()已经检查了,并且它抛出了与你将要抛出的相同的异常。

注意2:这个解决方案只适用于调用Count()方法比较廉价的情况。所有.NET集合都很好,因为它们都非常高效;对于特定的自定义/非标准集合可能会有问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接