开发者

How do I ensure a sequence has a certain length?

I want to check that an IEnumerable contains exactly one element. This snippet does work:

bool hasOneElement = seq.Count() == 1

However it's not very efficient, as Count() will enumerate the entire list. Obviously, knowing a list is empty or contains more开发者_如何学C than 1 element means it's not empty. Is there an extension method that has this short-circuiting behaviour?


This should do it:

public static bool ContainsExactlyOneItem<T>(this IEnumerable<T> source)
{
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        // Check we've got at least one item
        if (!iterator.MoveNext())
        {
            return false;
        }
        // Check we've got no more
        return !iterator.MoveNext();
    }
}

You could elide this further, but I don't suggest you do so:

public static bool ContainsExactlyOneItem<T>(this IEnumerable<T> source)
{
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        return iterator.MoveNext() && !iterator.MoveNext();
    }
}

It's the sort of trick which is funky, but probably shouldn't be used in production code. It's just not clear enough. The fact that the side-effect in the LHS of the && operator is required for the RHS to work appropriately is just nasty... while a lot of fun ;)

EDIT: I've just seen that you came up with exactly the same thing but for an arbitrary length. Your final return statement is wrong though - it should be return !en.MoveNext(). Here's a complete method with a nicer name (IMO), argument checking and optimization for ICollection/ICollection<T>:

public static bool CountEquals<T>(this IEnumerable<T> source, int count)
{
    if (source == null)
    {
        throw new ArgumentNullException("source");
    }
    if (count < 0)
    {
        throw new ArgumentOutOfRangeException("count",
                                              "count must not be negative");
    }
    // We don't rely on the optimizations in LINQ to Objects here, as
    // they have changed between versions.
    ICollection<T> genericCollection = source as ICollection<T>;
    if (genericCollection != null)
    {
        return genericCollection.Count == count;
    }
    ICollection nonGenericCollection = source as ICollection;
    if (nonGenericCollection != null)
    {
        return nonGenericCollection.Count == count;
    }
    // Okay, we're finally ready to do the actual work...
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        for (int i = 0; i < count; i++)
        {
            if (!iterator.MoveNext())
            {
                return false;
            }
        }
        // Check we've got no more
        return !iterator.MoveNext();
    }
}

EDIT: And now for functional fans, a recursive form of CountEquals (please don't use this, it's only here for giggles):

public static bool CountEquals<T>(this IEnumerable<T> source, int count)
{
    if (source == null)
    {
        throw new ArgumentNullException("source");
    }
    if (count < 0)
    {
        throw new ArgumentOutOfRangeException("count", 
                                              "count must not be negative");
    }
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        return IteratorCountEquals(iterator, count);
    }
}

private static bool IteratorCountEquals<T>(IEnumerator<T> iterator, int count)
{
    return count == 0 ? !iterator.MoveNext()
        : iterator.MoveNext() && IteratorCountEquals(iterator, count - 1);
}

EDIT: Note that for something like LINQ to SQL, you should use the simple Count() approach - because that'll allow it to be done at the database instead of after fetching actual results.


No, but you can write one yourself:

 public static bool HasExactly<T>(this IEnumerable<T> source, int count)
 {
   if(source == null)
      throw new ArgumentNullException("source");

   if(count < 0)
      return false;

   return source.Take(count + 1).Count() == count;
 }

EDIT: Changed from atleast to exactly after clarification.

For a more general and efficient solution (which uses only 1 enumerator and checks if the sequence implements ICollection or ICollection<T> in which case enumeration is not necessary), you might want to take a look at my answer here, which lets you specify whether you are looking forExact,AtLeast, orAtMost tests.


seq.Skip(1).Any() will tell you if the list has zero or one elements.

I think the edit you made is about the most efficient way to check the length is n. But there's a logic fault, items less than length long will return true. See what I've done to the second return statement.

    public static bool LengthEquals<T>(this IEnumerable<T> en, int length)
    {
        using (var er = en.GetEnumerator())
        {
            for (int i = 0; i < length; i++)
            {
                if (!er.MoveNext())
                    return false;
            }
            return !er.MoveNext();
        }
    }


How about this?

public static bool CountEquals<T>(this IEnumerable<T> source, int count) {
    return source.Take(count + 1).Count() == count;
}

The Take() will make sure we never call MoveNext more than count+1 times.

I'd like to note that for any instance of ICollection, the original implementation source.Count() == count should be faster because Count() is optimised to just look at the Count member.


I believe what you're looking for is .Single(). Anything other than exactly one will throw InvalidOperationException that you can catch.

http://msdn.microsoft.com/nb-no/library/bb155325.aspx

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜