如项目所说,LINQ to Objects 缺失了一些理想的功能。但MoreLinq
将强大的linq
进一步增强了,扩展出了将近100+的功能,使编写代码效率提高。
MoreLINQ项目的主要目标是为LINQ提供更多的功能和灵活性,以满足不同场景下的需求。该项目由一些微软的工程师创建和维护,他们利用自己的业余时间开发并分享这个开源项目。
Aggregate可实现对序列应用累加器,其原生方法是Linq的Aggregate
,MoreLinq在其基础上扩展了更多功能。
原生方法是这样的:
public static TSource Aggregate<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func);
public static TAccumulate Aggregate<TSource, TAccumulate>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func);
public static TResult Aggregate<TSource, TAccumulate, TResult>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func, Func<TAccumulate, TResult> resultSelector);
/// <summary>
/// Applies two accumulators sequentially in a single pass over a
/// sequence.
/// </summary>
/// <typeparam name="T">The type of elements in <paramref name="source"/>.</typeparam>
/// <typeparam name="TAccumulate1">The type of first accumulator value.</typeparam>
/// <typeparam name="TAccumulate2">The type of second accumulator value.</typeparam>
/// <typeparam name="TResult">The type of the accumulated result.</typeparam>
/// <param name="source">The source sequence</param>
/// <param name="seed1">The seed value for the first accumulator.</param>
/// <param name="accumulator1">The first accumulator.</param>
/// <param name="seed2">The seed value for the second accumulator.</param>
/// <param name="accumulator2">The second accumulator.</param>
/// <param name="resultSelector">
/// A function that projects a single result given the result of each
/// accumulator.</param>
/// <returns>The value returned by <paramref name="resultSelector"/>.</returns>
/// <remarks>
/// This operator executes immediately.
/// </remarks>
public static TResult Aggregate<T, TAccumulate1, TAccumulate2, TResult>(
this IEnumerable<T> source,
TAccumulate1 seed1, Func<TAccumulate1, T, TAccumulate1> accumulator1,
TAccumulate2 seed2, Func<TAccumulate2, T, TAccumulate2> accumulator2,
Func<TAccumulate1, TAccumulate2, TResult> resultSelector)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (accumulator1 == null) throw new ArgumentNullException(nameof(accumulator1));
if (accumulator2 == null) throw new ArgumentNullException(nameof(accumulator2));
if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector));
var a1 = seed1;
var a2 = seed2;
foreach (var item in source)
{
a1 = accumulator1(a1, item);
a2 = accumulator2(a2, item);
}
return resultSelector(a1, a2);
}
该扩展方法在一次遍历序列中连续应用累加器,上面的代码是对两个初始值和两个累加器的扩展,且sead1
只应用accumulator1
,同理sead2
只应用accumulator2
,然后对结果应用resultSelector(a1,a2)
方法。
该扩展方法有多个重载,最多可以在一次遍历中依次应用八个累加器序列。
/// <summary>
/// Applies two accumulator queries sequentially in a single
/// pass over a sequence.
/// </summary>
/// <typeparam name="T">The type of elements in <paramref name="source"/>.</typeparam>
/// <typeparam name="TResult1">The type of the result of the first accumulator.</typeparam>
/// <typeparam name="TResult2">The type of the result of the second accumulator.</typeparam>
/// <typeparam name="TResult">The type of the accumulated result.</typeparam>
/// <param name="source">The source sequence</param>
/// <param name="accumulator1">The first accumulator.</param>
/// <param name="accumulator2">The second accumulator.</param>
/// <param name="resultSelector">
/// A function that projects a single result given the result of each
/// accumulator.</param>
/// <returns>The value returned by <paramref name="resultSelector"/>.</returns>
/// <exception cref="InvalidOperationException">
/// An <see cref="IObservable{T}"/> returned by an accumulator function
/// produced zero or more than a single aggregate result.
/// </exception>
/// <remarks>
/// <para>This operator executes immediately.</para>
/// <para>
/// Each accumulator argument is a function that receives an
/// <see cref="IObservable{T}"/>, which when subscribed to, produces the
/// items in the <paramref name="source"/> sequence and in original
/// order; the function must then return an <see cref="IObservable{T}"/>
/// that produces a single aggregate on completion (when
/// <see cref="IObserver{T}.OnCompleted"/> is called. An error is raised
/// at run-time if the <see cref="IObserver{T}"/> returned by an
/// accumulator function produces no result or produces more than a
/// single result.
/// </para>
/// </remarks>
public static TResult Aggregate<T, TResult1, TResult2, TResult>(
this IEnumerable<T> source,
Func<IObservable<T>, IObservable<TResult1>> accumulator1,
Func<IObservable<T>, IObservable<TResult2>> accumulator2,
Func<TResult1, TResult2, TResult> resultSelector)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (accumulator1 == null) throw new ArgumentNullException(nameof(accumulator1));
if (accumulator2 == null) throw new ArgumentNullException(nameof(accumulator2));
if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector));
var r1 = new (bool, TResult1)[1];
var r2 = new (bool, TResult2)[1];
var subject = new Subject<T>();
using (SubscribeSingle(accumulator1, subject, r1, nameof(accumulator1)))
using (SubscribeSingle(accumulator2, subject, r2, nameof(accumulator2)))
{
foreach (var item in source)
subject.OnNext(item);
subject.OnCompleted();
}
return resultSelector (
GetAggregateResult(r1[0], nameof(accumulator1)),
GetAggregateResult(r2[0], nameof(accumulator2))
);
}
该方法在一个序列上的一次传递中按顺序应用累加器,上面的代码会分别订阅流数据,将执行结果应用resultSelector
方法。
所有数字的总和
偶数的总和
数字的个数
最小的数字
最大的数字
所有数字中不同数字的个数
数字列表
Enumerable
.Range(1, 10)
.Shuffle()
.Select(n => new { Num = n, Str = n.ToString(CultureInfo.InvariantCulture) })
.Aggregate(
0, (s, e) => s + e.Num,
0, (s, e) => e.Num % 2 == 0 ? s + e.Num : s,
0, (s, _) => s + 1,
(int?)null, (s, e) => s is int n ? Math.Min(n, e.Num) : e.Num,
(int?)null, (s, e) => s is int n ? Math.Max(n, e.Num) : e.Num,
new HashSet<int>(), (s, e) => { s.Add(e.Str.Length); return s; },
new List<(int, string)>(), (s, e) => { s.Add((e.Num, e.Str)); return s; },
(sum, esum, count, min, max, lengths, items) => new
{
Sum = sum,
EvenSum = esum,
Count = count,
Average = (double)sum / count,
Min = min is int mn ? mn : throw new InvalidOperationException(),
Max = max is int mx ? mx : throw new InvalidOperationException(),
UniqueLengths = "[" + string.Join(", ", lengths) + "]",
Items = "[" + string.Join(", ", items) + "]",
})
但用这种方式编写每个聚合器可能很繁琐、重复且容易出错,因为不能重复使用 Enumerable.Sum。Aggregate
扩展允许使用响应性推导式编写聚合器。将上边的代码改一下。
Enumerable
.Range(1, 10)
.Shuffle()
.Select(n => new { Num = n, Str = n.ToString(CultureInfo.InvariantCulture) })
.Aggregate(
s => s.Sum(e => e.Num),
s => s.Select(e => e.Num).Where(n => n % 2 == 0).Sum(),
s => s.Count(),
s => s.Min(e => e.Num),
s => s.Max(e => e.Num),
s => s.Select(e => e.Str.Length).Distinct().ToArray(),
s => s.ToList(),
(sum, esum, count, min, max, lengths, items) => new
{
Sum = sum,
EvenSum = esum,
Count = count,
Average = (double)sum / count,
Min = min,
Max = max,
UniqueLengths = "[" + string.Join(", ", lengths) + "]",
Items = "[" + string.Join(", ", items) + "]",
})