前言
本系列是对MoreLinq库的学习与总结,分析各个Api的实现方式及用法,也为能写出更高效的Linq打下基础。
AssertCount断言个数,如果列表个数与断言个数不同则抛异常
- 应用
void AssertCountTest()
{
var nums = Enumerable.Range(0, 2);
nums.AssertCount(10, (cmp, count) => new Exception($"出现异常:cmp={cmp},count={count}")).Dump();//出现异常:cmp=-1,count=10
}
断言列表个数与给定值是否一样, 不一样则执行自定义异常。自定义抛异常方法也可省略。
- 源码:
public static class MoreEnumerable
{
static readonly Func<int, int, Exception> DefaultErrorSelector = OnAssertCountFailure;
static Exception OnAssertCountFailure(int cmp, int count)
{
var message = cmp < 0
? "Sequence contains too few elements when exactly {0} were expected."
: "Sequence contains too many elements when exactly {0} were expected.";
return new SequenceException(string.Format(message, count.ToString("N0")));
//ToString("N0") 数字的千分位分隔符,如100,000,000
}
public static IEnumerable<TSource> AssertCount<TSource>(this IEnumerable<TSource> source, int count, Func<int, int, Exception> errorSelector) =>
AssertCountImpl(source, count, errorSelector);
public static IEnumerable<TSource> AssertCount<TSource>(this IEnumerable<TSource> source, int count) =>
AssertCountImpl(source, count, DefaultErrorSelector);
public static IEnumerable<TSource> AssertCountImpl<TSource>(IEnumerable<TSource> source, int count, Func<int, int, Exception> errorSelector)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
if (errorSelector == null) throw new ArgumentNullException(nameof(errorSelector));
return source.TryGetCollectionCount() is { } collectionCount
? collectionCount == count
? source
: From<TSource>(() => throw errorSelector(collectionCount.CompareTo(count), count))
: _();
IEnumerable<TSource> _()
{
var iterations = 0;
foreach (var element in source)
{
iterations++;
if (iterations > count)
throw errorSelector(1, count);
yield return element;
}
if (iterations != count)
throw errorSelector(-1, count);
}
}
public static int? TryGetCollectionCount<T>(this IEnumerable<T> source) => source switch
{
null => throw new ArgumentNullException(nameof(source)),
ICollection<T> collection => collection.Count,
IReadOnlyCollection<T> collection => collection.Count,
_ => null
};
public static IEnumerable<T> Evaluate<T>(this IEnumerable<Func<T>> functions) =>
from f in functions ?? throw new ArgumentNullException(nameof(functions))
select f();
public static IEnumerable<T> From<T>(params Func<T>[] functions)
{
if (functions == null) throw new ArgumentNullException(nameof(functions));
return Evaluate(functions);
}
}
public class SequenceException : Exception
{
const string DefaultMessage = "Error in sequence.";
public SequenceException() :
this(null)
{ }
public SequenceException(string? message) :
this(message, null)
{ }
public SequenceException(string? message, Exception? innerException) :
base(string.IsNullOrEmpty(message) ? DefaultMessage : message, innerException)
{ }
}
分析:
源码调用的量有些大,不过没关系,核心部分很简单
source.TryGetCollectionCount() is { } collectionCount
? collectionCount == count
? source
: From<TSource>(() => throw errorSelector(collectionCount.CompareTo(count), count))
: _();
首先判断是否是ICollection<T>,从方法TryGetCollectionCount<T>
可知如果是则直接返回个数,如果不是,则返回null。
情况1: 返回个数之后再判断collectionCount == count
是否相等,相等则直接返回列表,不相等则执行异常, From<TSource>(() => throw errorSelector(collectionCount.CompareTo(count), count))
。
注意执行抛异常的方法是From,最终执行的是from f in functions select f()
,即分别执行给定的action
情况2:返回null,说明不是ICollection<T>,则需要遍历执行列表,当遍历的个数大于断言的值时,执行异常操作,cmp=1。遍历结束之后遍历个数小于断言的值时,执行异常操作,cmp=-1。
查看默认的断言异常方法
var message = cmp < 0
? "Sequence contains too few elements when exactly {0} were expected."
: "Sequence contains too many elements when exactly {0} were expected.";
cmp 1和-1 的判断返回值,正是我们希望的断言语句。
另外: ToString("N0")
是将数字转为带有千分位分隔符的字符串,如100,000,000
作者:wwmin
微信公众号: DotNet技术说
本文链接:https://www.jianshu.com/p/ca4e75ab25c3
评论和私信会在第一时间回复。转载请注明出处!
如果您觉得文章对您有帮助,关注点赞,您的鼓励是博主的最大动力!
网友评论