Safely checking non-repeatable IEnumerables for emptiness

前端 未结 3 1590
既然无缘
既然无缘 2021-01-02 00:26

There are times when it\'s helpful to check a non-repeatable IEnumerable to see whether or not it\'s empty. LINQ\'s Any doesn\'t work well

相关标签:
3条回答
  • 2021-01-02 00:56

    You don't need to complicate it. A regular foreach loop with a single extra bool variable will do the trick.

    If you have

    if(input.Any())
    {
        A
        foreach(int i in input)
        {
            B
        }
        C
    }
    

    and you don't want to read input twice, you can change this to

    bool seenItem = false;
    foreach(int i in input)
    {
        if (!seenItem)
        {
            seenItem = true;
            A
        }
        B
    }
    if (seenItem)
    {
        C
    }
    

    Depending on what B does, you may be able to avoid the seenItem variable entirely.

    In your case, Enumerable.Zip is a fairly basic function that is easily reimplemented, and your replacement function can use something similar to the above.

    Edit: You might consider

    public static class MyEnumerableExtensions
    {
        public static IEnumerable<TFirst> NotReallyZip<TFirst, TSecond>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TFirst> resultSelector)
        {
            using (var firstEnumerator = first.GetEnumerator())
            using (var secondEnumerator = second.GetEnumerator())
            {
                if (secondEnumerator.MoveNext())
                {
                    if (firstEnumerator.MoveNext())
                    {
                        do yield return resultSelector(firstEnumerator.Current, secondEnumerator.Current);
                        while (firstEnumerator.MoveNext() && secondEnumerator.MoveNext());
                    }
                }
                else
                {
                    while (firstEnumerator.MoveNext())
                        yield return firstEnumerator.Current;
                }
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-02 00:57

    This is not an efficient solution if the enumeration is long, however it is an easy solution:

    var list = input.ToList();
    if (list.Count != 0) {
        foreach (var item in list) {
           ...
        }
    }
    
    0 讨论(0)
  • 2021-01-02 01:00

    You could also just read the first element and if it's not null, concatenate this first element with the rest of your input:

    var input = ReadNumbers();
    var first = input.FirstOrDefault();
    if (first != default(int)) //Assumes input doesn't contain zeroes
    {
        var firstAsArray = new[] {first};
        foreach (int i in firstAsArray.Concat(input))
        {
            // Will include the first element.
            Console.WriteLine(i);
        }
    }
    

    For a normal enumerable, the first element would be repeated twice, but for a non-repeatable enumerable it would work, unless iterating twice is not allowed. Also, if you had such an enumerator:

    private readonly static List<int?> Source = new List<int?>(){1,2,3,4,5,6};
    
    private static IEnumerable<int?> ReadNumbers()
    {
        while (Source.Count > 0) {
            yield return Source.ElementAt(0);
            Source.RemoveAt(0);
        }
    }
    

    Then it would print: 1, 1, 2, 3, 4, 5, 6. The reason being that the first element is consumed AFTER it has been returned. So the first enumerator, stopping at the first element, never has the chance of consuming that first element. But it would be a case of a badly written enumerator, here. If the element is consumed, then returned...

    while (Source.Count > 0) {
        var returnElement = Source.ElementAt(0);
        Source.RemoveAt(0);
        yield return returnElement;
    }
    

    ...you get the expected output of: 1, 2, 3, 4, 5, 6.

    0 讨论(0)
提交回复
热议问题