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
精彩评论