¿Es posible obtener una IEnumerator de un T []?
-
21-09-2019 - |
Pregunta
Vamos a decir que quiero crear una clase de colección que es seguro para subprocesos por defecto.
A nivel interno, la clase tiene una propiedad List<T>
protegida llamada Values
.
Para empezar, tiene sentido que la clase implemente ICollection<T>
. Algunos de los miembros de esta interfaz son bastante fáciles de implementar; por ejemplo, devoluciones Count
this.Values.Count
.
Pero la implementación de ICollection<T>
me obliga a implementar IEnumerable<T>
, así como IEnumerable
(no genérico), que es un poco complicado para una colección de hilos de proceso seguro.
Por supuesto, siempre podría lanzar una NotSupportedException
en IEnumerable<T>.GetEnumerator
y IEnumerable.GetEnumerator
, pero que se siente como una salida fácil para mí.
I ya tienen una función getValues
thread-safe que bloquea en Values
y devuelve una copia en la forma de una matriz T[]
. Así que mi idea era poner en práctica GetEnumerator
devolviendo this.getValues().GetEnumerator()
por lo que el siguiente código en realidad sería seguro para subprocesos:
ThreadSafeCollection coll = new ThreadSafeCollection ();
// add some items to coll
foreach (T value in coll) {
// do something with value
}
Por desgracia, esta aplicación sólo parece trabajo para IEnumerable.GetEnumerator
, no la versión genérica (y por lo tanto el código anterior arroja una InvalidCastException
).
Una de las ideas que tenía, que parecía el trabajo, fue a emitir el valor de retorno de T[]
getValues
a un IEnumerable<T>
antes de llamar GetEnumerator
en él. Una alternativa sería cambiar getValues
devolver un IEnumerable<T>
en el primer lugar, pero luego de la IEnumerable.GetEnumerator
no genérico simplemente emitir el valor de retorno de getValues
a un IEnumerable
no genérico. Pero en realidad no puedo decidir si estos enfoques se sienten descuidados o perfectamente aceptable.
En cualquier caso, ¿alguien tiene una mejor idea de cómo ir haciendo esto? He oído hablar de los métodos .Synchronized
, pero parece que sólo estará disponible para las colecciones no genéricas en el espacio de nombres System.Collections
. Tal vez hay una variante genérica de este que ya existe en .NET que simplemente no sepa?
Solución
La mayoría de las colecciones especifican que no se puede agregar o quitar cosas de la colección, mientras que la iteración. De ello se desprende que para una colección flujos seguros, que desea bloquear a cabo otros hilos de la modificación de la colección, mientras que cualquier tema es la iteración. Esto debería ser fácil de hacer con la sintaxis iteradores, y no le requiere para hacer una copia:
public IEnumerator<T> GetEnumerator()
{
lock (this.Values) // or an internal mutex you use for synchronization
{
foreach (T val in this.Values)
{
yield return val;
}
}
yield break;
}
Esto supone que todas las otras operaciones que podrían modificar la colección también Bloqueo this.Values
. Mientras que es verdad, que debe estar bien.
Otros consejos
Por desgracia, esta aplicación sólo parece trabajo para IEnumerable.GetEnumerator, no la versión genérica (y por lo tanto el código anterior arroja una InvalidCastException).
Parece extraño para mí. Tiene implementado no genérico IEnumerable explícitamente? Es decir. has escrito
public IEnumerator<T> GetEnumerator() { ...}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator<T>(); }
Además, has de implementar IEnumerable con iteradores sintaxis. Debe ser fácil:
public IEnumerator<T> GetEnumerator()
{
T[] values;
lock(this.Values)
values = this.Values.ToArray();
foreach(var value in values)
yield return value;
}
Si has probado, ¿por qué no se adapta a sus necesidades?
El tipo T[]
tiene una relacion especial a la System.Array
tipo de la que deriva. Se hicieron arreglos como T[]
antes de la introducción de genéricos en .NET (de lo contrario la sintaxis podría haber sido Array<T>
).
El tipo System.Array
tiene uno GetEnumerator()
método de instancia , public
. Este método devuelve IEnumerator
no genérico (desde .NET 1). Y System.Array
no tiene implementaciones de interfaces explícitas para GetEnumerator()
. Esto explica sus observaciones.
Sin embargo, cuando se introdujeron los genéricos en .NET 2.0, un especial de "truco" se hace para tener un T[]
implementar IList<T>
y sus interfaces de bases (de los cuales IEnumerable<T>
es uno). Por esta razón creo que es perfectamente razonable para el uso:
((IList<T>)(this.getValues())).GetEnumerator()
En la versión de .NET donde yo estoy comprobando esto, existen las siguientes clases:
namespace System
{
public abstract class Array
{
private sealed class SZArrayEnumerator // instance of this is returned with standard GetEnumerator() on a T[]
{
}
}
internal sealed class SZArrayHelper
{
private sealed class SZGenericArrayEnumerator<T> // instance of this is returned with generic GetEnumerator() on a T[] which has been cast to IList<T>
{
}
}
}