2012-03-08 9 views
10

Tengo una función que acepta un enumerable. Necesito asegurarme de que se evalúa al enumerador, pero prefiero no crear una copia (por ejemplo, a través de ToList() o ToArray()) si todo está listo en una lista o en alguna otra colección "inmovilizada". Por Frozen me refiero a colecciones donde el conjunto de elementos ya está establecido, p. List, Array, FsharpSet, Collection, etc., a diferencia de linq cosas como Select() y where().¿Es posible determinar si un IEnumerable <T> tiene ejecución diferida pendiente?

¿Es posible crear una función "ForceEvaluation" que pueda determinar si el enumerable ha diferido la ejecución pendiente, y luego evaluar el 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()} 

} 

}

Después de algunas investigaciones más me he dado cuenta de que esto es prácticamente imposible en cualquier sentido práctico, y requeriría la inspección código complejo de cada iterador.

Así que voy a ir con una variante de la respuesta de Marcos y crear una lista blanca de tipos seguros conocidos y simplemente llamar a ToList() cualquier cosa que no esté en eso no está en la lista blanca.

Gracias a todos por su ayuda.

Editar * Después de aún más reflexión, me he dado cuenta de que esto es equivalente al problema de detención. Muy imposible.

+0

Puede verificar 'if (foos is IList )', aunque esto probablemente sea un hack. Esto funciona incluso cuando 'foos' es una matriz. – dasblinkenlight

+0

¿Qué desea que suceda con 'while (true) {return return 1; } '? Sospecho que probablemente estés haciendo algo mal. Además, aquellos que están sugiriendo 'if (foos es' IList ')' no se están dando cuenta de que 'IList ' * puede * implementarse también de forma perezosa. – jason

+0

"Ejecución diferida" es un término tan amplio que puede significar casi cualquier cosa. ¿Está tratando de manejar un caso específico (es decir, 'IQueryable'). Si es así, compruebe el tipo 'IQueryable' y ejecute' ToList() 'contra eso). –

Respuesta

5

Usted podría tratar un cheque contra la esperanza de IList<T> o ICollection<T>, pero tenga en cuenta que estas puede todavía ser implementado con pereza - pero es mucho más raro, y LINQ no hace eso - sólo se utiliza iteradores (no perezoso colecciones). Por lo tanto:

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

Tenga en cuenta que esto es diferente a la normal .ToList(), que le devuelve una lista diferente cada vez, para asegurar que nada sucede inesperado.

La mayoría de los tipos de colección de hormigón (incluidos T[] y List<T>) satisfacen IList<T>. No estoy familiarizado con las colecciones de F #. Tendrás que verificarlo.

+0

FSharpSet es inmutable. :) –

+0

@Jason cosas inmutables todavía pueden implementar 'IList '; por ejemplo, 'ReadOnlyCollection ' hace. La pregunta es: * ¿FSharpSet lo hace? * –

+0

Ah, estaba diciendo que era inmutable, por lo que no había posibilidad de que tuviera ningún procesamiento diferido. :) Además, no, no implementa IList . –

1

Lo evitaría si quiere asegurarse de que esté "congelado". Ambos elementos Array y List <> se pueden cambiar en cualquier momento (es decir, la infame "colección cambiada durante la iteración"). Si realmente necesita asegurarse de que IEnumerable se evalúe Y no cambie debajo de su código, copie todos los elementos en su propia Lista/Matriz.

Puede haber otras razones para intentarlo, es decir, algunas operaciones dentro del tiempo de ejecución realizan comprobaciones especiales para que la colección sea una matriz para optimizarlas. O tiene una versión especial para interfaz especializada como ICollection o IQueryable además de IEnumerable genérico.

EDIT: Ejemplo de cambio de colección durante la iteración:

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

Un buen consejo, el enumerable está aislado en un solo hilo, (y los elementos dentro se manejan con un sistema de bloqueo, que es la razón por la que necesito asegurarme de que toda la lista se evalúa de antemano) –

+0

El enhebrado no está relacionado con la recopilación cambiado Es posible que no se aplique a su caso particular, pero tenga en cuenta que "no ejecución diferida" no es lo mismo que "no cambiará al azar mientras se itera". –

0

Si es posible utilizar un envoltorio en su caso, se podría hacer algo como esto

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 
} 

o poner en práctica el método de fuerza así si desea evaluar en cualquier caso

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

EDIT:

Si desea asegurarse de que la enumeración se evalúa sólo una vez, en todo caso, podría cambiar GetEnumerator a

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

Por favor, corrígeme si me equivoco, pero ¿no volvería a evaluar el enumerable una segunda vez cuando llamé a foreach (t en ForceableEnumerable )? –

+0

Sí. Supuse que llamarías a 'ForceEvaluation()' después de que posiblemente se haya evaluado la enumeración, no antes.Su pregunta fue sobre forzar la evaluación, no obstaculizarla. Sin embargo, puede cambiar 'GetEnumerator' si desea hacer eso. Ver mi edición –

+0

Tenga en cuenta que no utilizo 'foreach' sino' while', que no crea un enumerador automáticamente. –

6

Algo que funcionó para mí manera:

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

if (deffered.GetType().UnderlyingSystemType.Namespace.Equals("System.Linq")) 
{ 
    //this is a deffered executin IEnumerable 
} 
Cuestiones relacionadas