Search code examples
c#linqdynamicienumerableienumerator

How to return dynamic entity in GetEnumerator<T>


I have a class that implements IEnumerable<T>:

public class MyList<T> : IEnumerable<T>
{
    IQueryable Queryable;

    public MyList(IQueryable ts)
    {
        Queryable = ts;
    }

    public IEnumerator<T> GetEnumerator()
    {
        foreach (var item in Queryable)
        {
            yield return (T)item;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

When I select some property from IQueryable and do ToList(), it works:

IQueryable propertiesFromClasses = classesIQuerable.Select(a => a.Property);
MyList<MyClass> classes = new MyList<MyClass>(propertiesFromClasses);
var toListResult = classes.ToList();

Now, I want to select dynamic types:

IQueryable propertiesFromClasses = classesIQuerable.Select(a => new { SelectedProperty = a.Property });
MyList<MyClass> classes = new MyList<MyClass>(propertiesFromClasses);
var toListResult = classes.ToList();

It throws an exception at yield return (T)item; of GetEnumerator() method:

System.InvalidCastException: 'Unable to cast object of type '<>f__AnonymousType0`1[System.String]' to type 'MyClass'


Solution

  • The problem is obviously in the constructor of MyClass<T>.

    Make a proper constructor

    First of all, if you want to implement a class MyClass<Order> that represents an enumerable sequence of Order objects, why do you allow a constructor that accepts IQueryable<Student>?

    Wouldn't it be better to only accept a sequence of Orders, or at least a sequence of items that can be converted to Orders?

    This way your compiler will complain, instead of later during execution of the code.

    public class MyList<T> : IEnumerable<T>
    {
        IQueryable<T> queryable;
        public MyList(IQueryable<T> ts)
        {
            this.queryable = ts;
        }
        public IEnumerator<T> GetEnumerator()
        {
            return this.Queryable;
        }
        ...
    }
    

    If you'd done this, your compiler would already have complained, instead of a run-time exception.

    The object that you created in your 2nd piece of code is an object that implements IQueryable<someAnonymousClass>. Your debugger will tell you that this object does not implement IQueryable<MyClass>

    Objects that implement IQueryable<someAnonymousClass> also implements IQueryable. However, elements that can be enumerated from this IQueryable, are of type someAnonymousClass those objects can't be cast to MyClass without a proper conversion method.

    Your first piece of code does not lead to this problem, because the object that you created implements IQueryable<MyClass>, and thus the enumerator will yield MyClass objects, which can be converted to MyClass objects without problems.

    But again: if you had made a proper constructor, you would have noticed the problem at compile time, instead of at run-time.

    Alternative solution

    It could be that you really wanted to design a class that could hold a sequence of Students in and try to get extract all Orders from it, or a more realistic problem: put a sequence of Animals and enumerate all Birds. Such a class could extract all Birds from any sequence:

    public class FilteredEnumerator<T> : IEnumerable<T>
    {
        public FilteredEnumerator(IQueryable queryable)
        {
             this.queryable = queryable;
        }
    
        private readonly IQueryable queryable;
    
        private IEnumerator<T> GetEnumerator()
        {
            return this.queryable.OfType<T>();
        }
    }
    

    Although this would work. I'm not sure if it would be a very meaningful class. There is already a LINQ function that does what you want:

    var birds = myDbContext.Animals.OfType();