Ist es möglich, einen IEnumerator<T> von einem T[] zu erhalten?
-
21-09-2019 - |
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?
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>
{
}
}
}