Reputation: 10543
Let's say I have an object which may be of type IEnumerable<T>
. I want to write a method that returns true if the object is of type IEnumerable<T>
, is not null, and is not empty.
Here's what I've got so far:
public bool IsNullOrEmpty(object obj)
{
if (obj != null)
{
if (obj is IEnumerable<object>)
{
return (obj as IEnumerable<object>).Any();
}
}
return false;
}
This works if I pass in an object that is of type List<string>
, but not if I pass in an object that is of type List<int>
. It fails because because obj is IEnumerable<object>
returns false
.
Any idea how I can make this work for all generic IEnumerable
s?
Upvotes: 6
Views: 23873
Reputation: 9089
Since the type may be unknown, you can try check for IEnumerable
interface and use MoveNext()
on the enumerator.
EDIT: I updated the method name. It makes more sense with the logic now since the original question code was checking if there were items in the collection.
public bool IsNotNullOrEmpty(object enumerable)
{
if (enumerable is IEnumerable e)
{
using (var enumerator = e.GetEnumerator())
return enumerator.MoveNext();
}
return false;
}
Upvotes: 16
Reputation: 5007
This answers expands the question outside of the IEnumerable<T>
scope to "can this thing I have be used in a foreach
-loop?".
As you might known, foreach
does not check for IEnumerable
primarily, but rather tries to enumerate whatever is given.
This will work for all types which in some sense is enumerable in the sense that foreach
thinks:
public bool IsNullOrEmpty(object obj)
{
if (obj != null) // we can't be null
{
var method = obj.GetType().GetMethod("GetEnumerator");
if (method != null) // we have an enumerator method
{
var enumerator = method.Invoke(obj, null);
if (enumerator != null) // we got the enumerator
{
var moveMethod = enumerator.GetType().GetMethod("MoveNext")
if (moveMethod != null) // we have a movenext method, lets try it
return !(bool)moveMethod.Invoke(enumerator, null); // ie true = empty, false = has elements
}
}
}
return true; // it's empty, lets return true
}
This basically does the same as the foreach
-loop, ie checks for enumerability and doesn't really give a darn about the types involved.
As such it should really be avoided, but if you somehow have to check if you can put your type in foreach
-loop this should do the trick.
Upvotes: 0
Reputation: 8556
For completeness, adding the state-of-the-art answers for the recent c# versions (from c#8.0):
static bool IsNotNullOrEmpty<T>(object obj) => obj is IEnumerable<T> seq && seq.Any();
static bool IsNullOrEmpty<T>(object obj) => !(obj is IEnumerable<T> seq && seq.Any());
This assumes you will know T
in the calling site.
If you won't know T
, you shouldn't be using IEnumerable<object>
, but IEnumerable
instead. Something like this:
static bool IsNotNullOrEmpty(object obj) => obj is IEnumerable seq && seq.GetEnumerator().MoveNext();
Upvotes: 2
Reputation: 700302
You can first check if the object implements ICollection
like lists and arrays do, as checking the size of one of those is cheaper as they have a Count
property. If it's not you can check if it implements IEnumerable
and if it does, create an enumerator and see if you can move to the first item:
public static bool IsNullOrEmpty(object obj) {
ICollection c = obj as ICollection;
if (c != null) {
return c.Count == 0;
}
IEnumerable e = obj as IEnumerable;
if (e != null) {
return !e.GetEnumerator().MoveNext();
}
return false;
}
Upvotes: 1
Reputation: 144136
You can check for the non-generic IEnumerable
and check that for emptiness. You can add a check to ensure the object implements IEnumerable<T>
using reflection:
public static bool IsNullOrEmpty(object obj)
{
var e = obj as System.Collections.IEnumerable;
if (e == null || !e.GetType().GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))) return false;
foreach (object _ in e)
{
return true;
}
return false;
}
Upvotes: 3
Reputation: 460098
You can try to cast it to IEnumerable
:
public static bool IsNullOrEmpty<T>(this T obj) where T : class
{
if (obj == null) return true;
IEnumerable seq = obj as IEnumerable;
if (seq != null) return !seq.Cast<object>().Any();
return false;
}
...
List<int> list = new List<int>();
bool nullOrEmpty = list.IsNullOrEmpty(); // true
Btw, interestingly enough it works correctly with an empty string:
bool nullOrEmpty = "".IsNullOrEmpty(); // true
Upvotes: 3
Reputation: 20732
System.Collections.Generic.IEnumerable<T>
inherits from System.Collections.IEnumerable
- thus, if you are ok with checking the non-generic IEnumerable
, rather than the generic IEnumerable<T>
, you could just cast to IEnumerable
.
A few notes about your code: You are first checking with is
, and then you cast with as
. That is generally unnecessary; as
already checks and returns null
if the cast failed. Therefore, a shorter way would be:
var enumerable = obj as IEnumerable;
if (enumerable != null) {
return !enumerable.Cast<object>().Any();
}
Note that you will need the additional call to Cast
there, as Any
requires a generic IEnumerable<T>
.
Upvotes: 10