Is it possible to determine if an IEnumerable<T> has deffered execution pending?
Asked Answered
R

4

11

I have a function that accepts an Enumerable. I need to ensure that the enumerator is evaluated, but I'd rather not create a copy of it (e.g. via ToList() or ToArray()) if it is all ready in a List or some other "frozen" collection. By Frozen I mean collections where the set of items is already established e.g. List, Array, FsharpSet, Collection etc, as opposed to linq stuff like Select() and where().

Is it possible to create a function "ForceEvaluation" that can determine if the enumerable has deffered execution pending, and then evaluate the enumerable?

 public void Process(IEnumerable<Foo> foos)
 {
      IEnumerable<Foo> evalutedFoos = ForceEvaluation(foos)
      EnterLockedMode(); // all the deferred processing needs to have been done before this line. 
      foreach (Foo foo in foos) 
      {
           Bar(foo);
      }  
}

 public IEnumerable ForceEvaluation(IEnumerable<Foo> foos)
 {
      if(??????)
      { return foos}
      else
      {return foos.ToList()}

 }

}

After some more research I've realized that this is pretty much impossible in any practical sense, and would require complex code inspection of each iterator.

So I'm going to go with a variant of Mark's answer and create a white-list of known safe types and just call ToList() anything not on that is not on the white-list.

Thank you all for your help.

Edit* After even more reflection, I've realized that this is equivalent to the halting problem. So very impossible.

Risky answered 8/3, 2012 at 20:56 Comment(6)
You can check if (foos is IList<Foo>), although this is probably a hack. This works even when foos is an array.Carder
What do you want to have happen with while(true) { yield return 1; }? I suspect you're probably doing something wrong. Also, those that are suggesting if(foos is IList<Foo>) aren't realizing that IList<Foo> can be lazily implemented too.Religiose
"Deferred Execution" is such a broad term, it can mean just about anything. Are you trying to handle a specific case (i.e. IQueryable). If so, then check for the IQueryable type and run ToList() against that).Backhand
@Jason What do you want to have happen with while(true) { yield return 1; }? I'd be happy if it got stuck in a loop and then crashed on the foos.ToList()Risky
@Brian I'm not trying to handle the general case. I know it is very broad. I suspect that it may not be possible.Risky
What about for(int i = 0; i < 42; i++) yield return Randon.Next(); where every execution produces a completely different sequence?Religiose
P
5

You could try a hopeful check against IList<T> or ICollection<T>, but note that these can still be implemented lazily - but it is much rarer, and LINQ doesn't do that - it just uses iterators (not lazy collections). So:

var list = foos as IList<Foo>;
if(list != null) return list; // unchanged
return foos.ToList();

Note that this is different to the regular .ToList(), which gives you back a different list each time, to ensure nothing unexpected happens.

Most concrete collection types (including T[] and List<T>) satisfy IList<T>. I'm not familiar with the F# collections - you'd need to check that.

Plan answered 8/3, 2012 at 21:0 Comment(4)
@Jason immutable things can still implement IList<T>; for example, ReadOnlyCollection<T> does. The question is : does FSharpSet do so?Plan
Ah, I was stating that it was immutable, so there was no chance of it having any deferred processing. :) Also, no it does not implement IList<T>.Risky
@Jason: Actually, on the contrary, immutability and deferred execution go together quite nicely. I don't know what F# does, but in Haskell all lists are both immutable and lazily computed by default.Darbee
It might be good to look for non-generic ICollection as well as the generic, so as to recognize that an IEnumerable<Animal> that identifies a List<SiameseCat> shouldn't need to have ToList invoked upon it despite the fact that List<SiameseCat> does not implement IList<Animal> nor ICollection<Animal>.Horrified
E
6

Something that worked for me way :

IEnumerable<t> deffered = someArray.Where(somecondition);

if (deffered.GetType().UnderlyingSystemType.Namespace.Equals("System.Linq"))
{
  //this is a deffered executin IEnumerable
}
Enkindle answered 19/11, 2012 at 12:1 Comment(0)
P
5

You could try a hopeful check against IList<T> or ICollection<T>, but note that these can still be implemented lazily - but it is much rarer, and LINQ doesn't do that - it just uses iterators (not lazy collections). So:

var list = foos as IList<Foo>;
if(list != null) return list; // unchanged
return foos.ToList();

Note that this is different to the regular .ToList(), which gives you back a different list each time, to ensure nothing unexpected happens.

Most concrete collection types (including T[] and List<T>) satisfy IList<T>. I'm not familiar with the F# collections - you'd need to check that.

Plan answered 8/3, 2012 at 21:0 Comment(4)
@Jason immutable things can still implement IList<T>; for example, ReadOnlyCollection<T> does. The question is : does FSharpSet do so?Plan
Ah, I was stating that it was immutable, so there was no chance of it having any deferred processing. :) Also, no it does not implement IList<T>.Risky
@Jason: Actually, on the contrary, immutability and deferred execution go together quite nicely. I don't know what F# does, but in Haskell all lists are both immutable and lazily computed by default.Darbee
It might be good to look for non-generic ICollection as well as the generic, so as to recognize that an IEnumerable<Animal> that identifies a List<SiameseCat> shouldn't need to have ToList invoked upon it despite the fact that List<SiameseCat> does not implement IList<Animal> nor ICollection<Animal>.Horrified
U
1

I would avoid it if you want to make sure it is "frozen". Both Array elements and List<> can be changed at any time (i.e. infamous "collection changed during iteration" exception). If you really need to make sure IEnumerable is evaluated AND not changing underneath your code than copy all items into your own List/Array.

There could be other reasons to try it - i.e. some operations inside run time do special checks for collection being an array to optimize them. Or have special version for specialized interface like ICollection or IQueryable in addition to generic IEnumerable.

EDIT: Example of collection changing during iteration:

IEnumerable<T> collectionAsEnumrable = collection;
foreach(var i in collectionAsEnumrable)
{
   // something like following can be indirectly called by 
   // synchronous method on the same thread
   collection.Add(i.Clone());
   collection[3] = 33;
}
Unhappy answered 8/3, 2012 at 21:10 Comment(2)
Good advice, the enumerable itself is isolated to a single thread,(and the items within are managed with a locking system, which is the reason I need to make sure the whole list is evaluated beforehand)Risky
The threading is unrelated to collection being changed. It may not apply for your particular case, but beware that "no deferred execution" is not the same as "will not randomly change while iterating".Unhappy
G
0

If it is possible to use a wrapper in your case, you could do something like this

public class ForceableEnumerable<T> : IEnumerable<T>
{
    IEnumerable<T> _enumerable;
    IEnumerator<T> _enumerator;

    public ForceableEnumerable(IEnumerable<T> enumerable)
    {
        _enumerable = enumerable;
    }

    public void ForceEvaluation()
    {
        if (_enumerator != null) {
            while (_enumerator.MoveNext()) {
            }
        }
    }

    #region IEnumerable<T> Members

    public IEnumerator<T> GetEnumerator()
    {
        _enumerator = _enumerable.GetEnumerator();
        return _enumerator;
    }

    #endregion

    #region IEnumerable Members

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

    #endregion
}

Or implement the force method like this if you want to evaluate in any case

public void ForceEvaluation()
{
    if (_enumerator == null) {
        _enumerator = _enumerable.GetEnumerator();
    }
    while (_enumerator.MoveNext()) {
    }
}

EDIT:

If you want to ensure that the enumeration is evaluated only once in any case, you could change GetEnumerator to

public IEnumerator<T> GetEnumerator()
{
   if (_enumerator == null) }
       _enumerator = _enumerable.GetEnumerator();
   }
   return _enumerator;
} 
Geter answered 8/3, 2012 at 21:27 Comment(3)
Please correct me if I'm wrong, but wouldn't this just re-evaluate the enumerable a 2nd time when I called foreach(t in ForceableEnumerable<t>)?Risky
Yes. I assumed that you would call ForceEvaluation() after the enumeration has possibly been evaluated, not before. Your question was about forcing evaluation, not hindering it. However, you can change GetEnumerator if you want to do that. See my edit.Geter
Note that I do not use foreach but while, which does not create an enumerator automatically.Geter

© 2022 - 2024 — McMap. All rights reserved.