我有一个具有Nullable DateOfBirth属性的Person对象。是否有一种方法可以使用LINQ来查询Person对象列表中最早/最小的DateOfBirth值?

这是我的开场白:

var firstBornDate = People.Min(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue));

Null DateOfBirth值被设置为DateTime。MaxValue,以便将它们排除在Min考虑之外(假设至少有一个具有指定的DOB)。

但是所有这些对我来说都是将firstBornDate设置为DateTime值。我想要的是与之匹配的Person对象。我是否需要像这样写第二个查询:

var firstBorn = People.Single(p=> (p.DateOfBirth ?? DateTime.MaxValue) == firstBornDate);

或者有没有更精简的方法?


当前回答

如果要选择具有最小或最大属性值的对象。另一种方法是使用实现IComparable。

public struct Money : IComparable<Money>
{
   public Money(decimal value) : this() { Value = value; }
   public decimal Value { get; private set; }
   public int CompareTo(Money other) { return Value.CompareTo(other.Value); }
}

最大执行将是。

var amounts = new List<Money> { new Money(20), new Money(10) };
Money maxAmount = amounts.Max();

最小实现将是。

var amounts = new List<Money> { new Money(20), new Money(10) };
Money maxAmount = amounts.Min();

通过这种方式,您可以比较任何对象并在返回对象类型的同时获得Max和Min。

希望这能帮助到一些人。

其他回答

你可以使用现有的linq扩展,比如MoreLinq。但如果你只需要这些方法,那么你可以使用这里的简单代码:

public static IEnumerable<T> MinBys<T>(this IEnumerable<T> collection, Func<T, IComparable> selector)
{
    var dict = collection.GroupBy(selector).ToDictionary(g => g.Key);
    return dict[dict.Keys.Min()];
}
public static IEnumerable<T> MaxBys<T>(this IEnumerable<T> collection, Func<T, IComparable> selector)
{
    var dict = collection.GroupBy(selector).ToDictionary(g => g.Key);
    return dict[dict.Keys.Max()];
}

不幸的是,没有内置的方法来做到这一点,但它很容易为自己实现。以下是它的核心内容:

public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source,
    Func<TSource, TKey> selector)
{
    return source.MinBy(selector, null);
}

public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source,
    Func<TSource, TKey> selector, IComparer<TKey> comparer)
{
    if (source == null) throw new ArgumentNullException("source");
    if (selector == null) throw new ArgumentNullException("selector");
    comparer ??= Comparer<TKey>.Default;

    using (var sourceIterator = source.GetEnumerator())
    {
        if (!sourceIterator.MoveNext())
        {
            throw new InvalidOperationException("Sequence contains no elements");
        }
        var min = sourceIterator.Current;
        var minKey = selector(min);
        while (sourceIterator.MoveNext())
        {
            var candidate = sourceIterator.Current;
            var candidateProjected = selector(candidate);
            if (comparer.Compare(candidateProjected, minKey) < 0)
            {
                min = candidate;
                minKey = candidateProjected;
            }
        }
        return min;
    }
}

使用示例:

var firstBorn = People.MinBy(p => p.DateOfBirth ?? DateTime.MaxValue);

请注意,如果序列为空,将抛出异常,如果有多个元素,则返回第一个具有最小值的元素。

或者,你也可以在MinBy.cs中使用MoreLINQ中的实现。(当然,有一个相应的MaxBy。)

通过包管理器控制台安装:

PM>安装包morelinq

People.Aggregate((curMin, x) => (curMin == null || (x.DateOfBirth ?? DateTime.MaxValue) <
    curMin.DateOfBirth ? x : curMin))

聚合的简单使用(相当于其他语言中的折叠):

var firstBorn = People.Aggregate((min, x) => x.DateOfBirth < min.DateOfBirth ? x : min);

唯一的缺点是每个sequence元素访问属性两次,这可能会很昂贵。这很难解决。

如果要选择具有最小或最大属性值的对象。另一种方法是使用实现IComparable。

public struct Money : IComparable<Money>
{
   public Money(decimal value) : this() { Value = value; }
   public decimal Value { get; private set; }
   public int CompareTo(Money other) { return Value.CompareTo(other.Value); }
}

最大执行将是。

var amounts = new List<Money> { new Money(20), new Money(10) };
Money maxAmount = amounts.Max();

最小实现将是。

var amounts = new List<Money> { new Money(20), new Money(10) };
Money maxAmount = amounts.Min();

通过这种方式,您可以比较任何对象并在返回对象类型的同时获得Max和Min。

希望这能帮助到一些人。