K Mehta
K Mehta

Reputation: 10543

Checking if a generic IEnumerable is empty

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 IEnumerables?

Upvotes: 6

Views: 23873

Answers (7)

TyCobb
TyCobb

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

flindeberg
flindeberg

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

Rodrigo Rodrigues
Rodrigo Rodrigues

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

Guffa
Guffa

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

Lee
Lee

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

Tim Schmelter
Tim Schmelter

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

O. R. Mapper
O. R. Mapper

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

Related Questions