美文网首页
c# MoreLinq 之 AssertCount

c# MoreLinq 之 AssertCount

作者: wwmin_ | 来源:发表于2021-02-26 08:34 被阅读0次

前言

本系列是对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
评论和私信会在第一时间回复。转载请注明出处!
如果您觉得文章对您有帮助,关注点赞,您的鼓励是博主的最大动力!

相关文章

网友评论

      本文标题:c# MoreLinq 之 AssertCount

      本文链接:https://www.haomeiwen.com/subject/jshpfltx.html