Dilshod K
Dilshod K

Reputation: 3032

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'

Upvotes: -1

Views: 283

Answers (1)

Harald Coppoolse
Harald Coppoolse

Reputation: 30502

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();

Upvotes: 0

Related Questions