Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force IEnumerable<T> to evaluate without calling .ToArray() or .ToList()

If I query EF using something like this...

IEnumerable<FooBar> fooBars = db.FooBars.Where(o => o.SomeValue == something);

IIRC, This creates a lazy-evaluated, iterable state machine in the background, that does not yet contain any results; rather, it contains an expression of "how" to obtain the results when required.

If I want to force the collection to contain results I have to call .ToArray() or .ToList()

Is there a way to force an IEnumerable<T> collection to contain results without calling .ToArray() or .ToList(); ?

Rationale

I don't know if the CLR is capable of doing this, but essentially I want to forcibly create an evaluated collection that implements the IEnumerable<T> interface, but is implemented under the hood by the CLR, thus NOT a List<T> or Array<T>

Presumably this is not possible, since I'm not aware of any CLR capability to create in-memory, evaluated collections that implement IEnumerable<T>

Proposal

Say for example, I could write something like this:

var x = IEnumerable<FooBar> fooBars = db.FooBars
        .Where(o => o.SomeValue == something)
        .Evaluate(); // Does NOT return a "concrete" impl such as List<T> or Array<T>

Console.WriteLine(x.GetType().Name);
// eg. <EvaluatedEnumerable>e__123
like image 264
Matthew Layton Avatar asked Jul 25 '16 08:07

Matthew Layton


3 Answers

Is there a way to force an IEnumerable<T> collection to contain results without calling .ToArray() or .ToList(); ?

Yes, but it is perhaps not what you want:

IEnumerable<T> source = …;
IEnumerable<T> cached = new List<T>(source);

The thing is, IEnumerable<T> is not a concrete type. It is an interface (contract) representing an item sequence. There can be any concrete type "hiding behind" this interface; some might only represent a query, others actually hold the queried items in memory.

If you want to force-evaluate your sequence so that the result is actually stored in physical memory, you need to make sure that the concrete type behind IEnumerable<T> is a in-memory collection that holds the results of the evaluation. The above code example does just that.

like image 71
stakx - no longer contributing Avatar answered Nov 04 '22 22:11

stakx - no longer contributing


You can use a foreach loop:

foreach (var item in fooBars) { }

Note that this evaluates all items in fooBars, but throws away the result immediately. Next time you run the same foreach loop or .ToArray(), .ToList(), the enumerable will be evaluated once again.

like image 45
Salah Akbari Avatar answered Nov 04 '22 22:11

Salah Akbari


A concrete use case I've run into revolves around needing to ensure that an IEnumerable that wraps a DB Query has begun returning results (indicating that the query did not time out) before returning control to the calling method. But the results are too large to evaluate fully, hence the IEnumerable to support streaming.

internal class EagerEvaluator<T>
{
    private readonly T _first;
    private readonly IEnumerator<T> _enumerator;
    private readonly bool _hasFirst;

    public EagerEvaluator(IEnumerable<T> enumerable)
    {
        _enumerator = enumerable.GetEnumerator();
        if (_enumerator.MoveNext())
        {
            _hasFirst = true;
            _first = _enumerator.Current;                
        }
    }

    public IEnumerable<T> ToEnumerable()
    {
        if (_hasFirst)
        {
            yield return _first;

            while (_enumerator.MoveNext())
            {
                yield return _enumerator.Current;                
            }
        }
    }
}

The usage is pretty straight forward:

IEnumerable<FooBar> fooBars = new EagerEvaluator(fooBars).ToEnumerable()
like image 25
Nick Avatar answered Nov 04 '22 21:11

Nick