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?

¿Fue útil?

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>
    {
    }
  }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top