我有一个具有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);
或者有没有更精简的方法?
你可以使用现有的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()];
}
下面是更通用的解决方案。它本质上做相同的事情(以O(N)顺序),但对任何IEnumerable类型,并且可以与属性选择器可以返回null的类型混合。
public static class LinqExtensions
{
public static T MinBy<T>(this IEnumerable<T> source, Func<T, IComparable> selector)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
if (selector == null)
{
throw new ArgumentNullException(nameof(selector));
}
return source.Aggregate((min, cur) =>
{
if (min == null)
{
return cur;
}
var minComparer = selector(min);
if (minComparer == null)
{
return cur;
}
var curComparer = selector(cur);
if (curComparer == null)
{
return min;
}
return minComparer.CompareTo(curComparer) > 0 ? cur : min;
});
}
}
测试:
var nullableInts = new int?[] {5, null, 1, 4, 0, 3, null, 1};
Assert.AreEqual(0, nullableInts.MinBy(i => i));//should pass