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

喜你入骨 提交于 2019-12-08 16:41:09

问题


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.


回答1:


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.




回答2:


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
}



回答3:


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;
}



回答4:


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;
} 


来源:https://stackoverflow.com/questions/9624805/is-it-possible-to-determine-if-an-ienumerablet-has-deffered-execution-pending

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!