Можно ли определить, отложил ли IEnumerable <T>отложенное выполнение? - PullRequest
10 голосов
/ 09 марта 2012

У меня есть функция, которая принимает Enumerable.Мне нужно убедиться, что перечислитель вычислен, но я бы не стал создавать его копию (например, через ToList () или ToArray ()), если он все готов в List или какой-либо другой «замороженной» коллекции.Под «Замороженным» я подразумеваю коллекции, в которых набор элементов уже установлен, например, List, Array, FsharpSet, Collection и т. Д., В отличие от linq, например Select () и where ().

Возможно ли создать функцию«ForceEvaluation», которая может определить, отложено ли перечисляемое отложенное выполнение, а затем оценить перечисляемое?

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

 }

}

После еще одного исследования я понял, что это почти невозможнов любом практическом смысле, и потребует сложной проверки кода каждого итератора.

Итак, я собираюсь пойти с вариантом ответа Марка и создать белый список известных безопасных типов и просто вызвать ToList ()все, чего нет, не занесено в белый список.

Спасибо всем за помощь.

Редактировать * После еще большего размышления я понял, что это эквивалентно проблеме остановки,Так очень невозможно.

Ответы [ 4 ]

6 голосов
/ 19 ноября 2012

Что-то, что сработало для меня так:

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

if (deffered.GetType().UnderlyingSystemType.Namespace.Equals("System.Linq"))
{
  //this is a deffered executin IEnumerable
}
5 голосов
/ 09 марта 2012

Вы могли бы попробовать многообещающую проверку против IList<T> или ICollection<T>, но учтите, что эти могут все еще быть реализованы лениво - но это гораздо реже, и LINQ несделать это - он просто использует итераторы (не ленивые коллекции).Итак:

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

Обратите внимание, что это отличается от обычного .ToList(), который каждый раз возвращает вам новый список, чтобы гарантировать, что ничего неожиданного не произойдет.

Большинство конкретных типов коллекций (в том числе T[] и List<T>) удовлетворяют IList<T>.Я не знаком с коллекциями F # - вам нужно это проверить.

1 голос
/ 09 марта 2012

Я бы избежал этого, если вы хотите убедиться, что он "заморожен". Элементы Array и List <> могут быть изменены в любое время (то есть печально известное исключение «коллекция изменена во время итерации»). Если вам действительно нужно убедиться, что IEnumerable оценивается и не изменяется под вашим кодом, скопируйте все элементы в свой собственный список / массив.

Могут быть и другие причины, чтобы попробовать это - то есть некоторые операции во время выполнения делают специальные проверки для сбора, являющегося массивом, чтобы оптимизировать их. Или иметь специальную версию для специализированного интерфейса, такого как ICollection или IQueryable, в дополнение к универсальному IEnumerable.

РЕДАКТИРОВАТЬ: Пример изменения коллекции во время итерации:

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;
}
0 голосов
/ 09 марта 2012

Если в вашем случае можно использовать обертку, вы можете сделать что-то вроде этого

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
}

Или примените метод Force, как этот, если вы хотите выполнить оценку в любом случае

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

EDIT:

Если вы хотите, чтобы перечисление оценивалось только один раз в любом случае, вы можете изменить GetEnumerator на

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