Frage

Nehmen wir an, ich möchte eine Sammlungsklasse erstellen, die standardmäßig threadsicher ist.

Intern ist die Klasse geschützt List<T> Eigenschaft genannt Values.

Für den Anfang ist es sinnvoll, die Klasse implementieren zu lassen ICollection<T>.Einige Mitglieder dieser Schnittstelle sind recht einfach zu implementieren.Zum Beispiel, Count kehrt zurück this.Values.Count.

Aber umsetzen ICollection<T> erfordert, dass ich es umsetze IEnumerable<T> sowie IEnumerable (nicht generisch), was für eine Thread-sichere Sammlung etwas schwierig ist.

Natürlich könnte ich immer einen werfen NotSupportedException An IEnumerable<T>.GetEnumerator Und IEnumerable.GetEnumerator, aber das fühlt sich für mich wie eine Ausrede an.

Ich habe bereits einen Thread-Safe getValues Funktion, die einrastet Values und gibt eine Kopie in Form von a zurück T[] Array.Meine Idee war also die Umsetzung GetEnumerator durch Rückkehr this.getValues().GetEnumerator() damit der folgende Code tatsächlich threadsicher wäre:

ThreadSafeCollection coll = new ThreadSafeCollection ();

// add some items to coll

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

Leider scheint diese Implementierung nur für zu funktionieren IEnumerable.GetEnumerator, nicht die generische Version (und daher löst der obige Code eine aus InvalidCastException).

Eine Idee, die ich hatte und die zu funktionieren schien, war, das zu besetzen T[] Rückgabewert von getValues zu einem IEnumerable<T> bevor Sie anrufen GetEnumerator drauf.Eine Alternative wäre ein Wechsel getValues eine zurückgeben IEnumerable<T> in erster Linie, aber dann für das Nicht-Generische IEnumerable.GetEnumerator Wandeln Sie einfach den Rückgabewert um getValues zu einem Nicht-Generikum IEnumerable.Aber ich kann mich nicht wirklich entscheiden, ob sich diese Ansätze schlampig oder vollkommen akzeptabel anfühlen.

Hat auf jeden Fall jemand eine bessere Idee, wie das geht?Ich habe davon gehört .Synchronized Methoden, aber sie scheinen nur für nicht generische Sammlungen in der verfügbar zu sein System.Collections Namensraum.Vielleicht gibt es eine generische Variante davon, die bereits in .NET existiert und die ich einfach nicht kenne?

War es hilfreich?

Lösung

Die meisten Sammlungen geben an, dass Sie während der Iteration keine Dinge zur Sammlung hinzufügen oder daraus entfernen können.Daraus folgt, dass Sie für eine Thread-sichere Sammlung andere Threads daran hindern möchten, die Sammlung zu ändern, während ein Thread iteriert.Dies sollte mit der Syntax des Iterators einfach zu bewerkstelligen sein und erfordert nicht, dass Sie eine Kopie erstellen:

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

Dies setzt voraus, dass alle anderen Vorgänge, die die Sammlung möglicherweise ändern, ebenfalls gesperrt werden this.Values.Solange das wahr ist, sollte es dir gut gehen.

Andere Tipps

Leider scheint diese Implementierung nur für IEnumerable.GetEnumerator zu funktionieren, nicht für die generische Version (und daher löst der obige Code eine InvalidCastException aus).

Kommt mir seltsam vor.Haben Sie nicht generisches IEnumerable explizit implementiert?D.h.hast du geschrieben

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

Haben Sie auch versucht, IEnumerable mit der Iteratorsyntax zu implementieren?Es sollte einfach sein:

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

Wenn Sie es versucht haben, warum entspricht es nicht Ihren Anforderungen?

Der Typ T[] hat eine besondere Beziehung zum Typ System.Array von dem es abgeleitet ist.Arrays wie T[] wurden erstellt, bevor Generika in .NET eingeführt wurden (andernfalls hätte die Syntax anders sein können). Array<T>).

Der Typ System.Array hat eins GetEnumerator() Instanzmethode, public.Diese Methode gibt nicht generische Werte zurück IEnumerator (seit .NET 1).Und System.Array hat keine expliziten Schnittstellenimplementierungen für GetEnumerator().Das erklärt Ihre Beobachtungen.

Aber als in .NET 2.0 Generika eingeführt wurden, wurde ein spezieller „Hack“ entwickelt, um eine zu haben T[] implementieren IList<T> und seine Basisschnittstellen (davon IEnumerable<T> ist ein).Aus diesem Grund halte ich es für absolut sinnvoll, Folgendes zu verwenden:

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

In der Version von .NET, in der ich dies überprüfe, sind die folgenden Klassen vorhanden:

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>
    {
    }
  }
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top