Question

Disons que je veux créer une classe de collection qui est thread-safe par défaut.

En interne, la classe possède une propriété protégée appelée List<T> Values.

Pour commencer, il est logique d'avoir la classe ICollection<T> mettre en œuvre. Certains des membres de cette interface sont assez faciles à mettre en œuvre; par exemple, Count retourne this.Values.Count.

Mais la mise en œuvre ICollection<T> me oblige à mettre en œuvre IEnumerable<T> ainsi que IEnumerable (non générique), ce qui est un peu difficile pour une collection thread-safe.

Bien sûr, je pourrais toujours jeter un NotSupportedException sur IEnumerable<T>.GetEnumerator et IEnumerable.GetEnumerator, mais qui se sent comme un flic-out pour moi.

Je l'ai déjà une fonction getValues thread-safe qui verrouille sur Values et retourne une copie sous la forme d'un tableau de T[]. Donc, mon idée était de mettre en œuvre GetEnumerator en retournant this.getValues().GetEnumerator() afin que le code suivant serait effectivement thread-safe:

ThreadSafeCollection coll = new ThreadSafeCollection ();

// add some items to coll

foreach (T value in coll) {
    // do something with value
}

Malheureusement, cette mise en œuvre semble ne fonctionner que pour IEnumerable.GetEnumerator, et non pas la version générique (et donc le code ci-dessus renvoie une InvalidCastException).

Une idée que j'avais, ce qui semblait fonctionner, était de jeter la valeur de retour de T[] de getValues à un IEnumerable<T> avant d'appeler GetEnumerator là-dessus. Une alternative serait de changer getValues pour retourner un IEnumerable<T> en premier lieu, mais pour la IEnumerable.GetEnumerator non-générique simplement jeter la valeur de retour de getValues à un IEnumerable non générique. Mais je ne peux pas décider vraiment si ces approches se sentent bâclée ou tout à fait acceptable.

Dans tous les cas, quelqu'un at-il une meilleure idée de comment s'y prendre pour le faire? Je l'ai entendu parler des méthodes de .Synchronized, mais ils semblent être uniquement disponible pour les collections non génériques dans l'espace de noms System.Collections. Peut-être il y a une variante générique de ce qui existe déjà dans .NET que je ne sais pas simplement au sujet?

Était-ce utile?

La solution

La plupart des collections précisent que vous ne pouvez pas ajouter ou supprimer des choses de la collection tout en réitérant. Il en résulte que pour une collection thread-safe, vous voulez verrouiller les autres threads de modifier la collection en tout fil est une itération. Cela devrait être facile à faire avec la syntaxe itérateurs, et ne nécessite pas de faire une copie:

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

Cela suppose que toutes les autres opérations qui pourraient modifier la collection également this.Values serrure. Tant que cela est vrai, vous devriez être bien.

Autres conseils

  

Malheureusement, cette mise en œuvre semble ne fonctionner que pour IEnumerable.GetEnumerator, et non pas la version générique (et donc le code ci-dessus renvoie une InvalidCastException).

Semble étrange pour moi. Avez-vous mis IEnumerable non générique explicitement? C'est à dire. avez-vous écrit

public IEnumerator<T> GetEnumerator() { ...}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator<T>(); }

En outre, vous avez essayé de mettre en œuvre IEnumerable avec la syntaxe itérateurs. Il devrait être facile:

public IEnumerator<T> GetEnumerator()
{
    T[] values;
    lock(this.Values)
        values = this.Values.ToArray();
    foreach(var value in values)
        yield return value;
}

Si vous avez essayé, pourquoi ne pas répond à vos besoins?

Le type T[] a une realtionship particulière au type System.Array dont elle dérive. Les tableaux comme T[] ont été faites avant les médicaments génériques ont été introduits dans .NET (sinon la syntaxe aurait pu être Array<T>).

Le type System.Array a une instance GetEnumerator() procédé , public. Cette méthode retourne IEnumerator non générique (depuis .NET 1). Et System.Array n'a pas implémentations d'interface explicites pour GetEnumerator(). Ceci explique vos observations.

Mais quand les médicaments génériques ont été introduits dans .NET 2.0, a été fait une spéciale « hack » d'avoir un T[] mettre en œuvre IList<T> et ses interfaces de base (dont IEnumerable<T> est l'un). Pour cette raison, je pense qu'il est tout à fait raisonnable d'utiliser:

((IList<T>)(this.getValues())).GetEnumerator()

Dans la version de .NET où je vérifie cela, les classes suivantes existent:

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>
    {
    }
  }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top