Domanda

Diciamo che voglio creare una classe collezione che è thread-safe per impostazione predefinita.

Internamente, la classe ha una proprietà List<T> protetta denominata Values.

Per cominciare, ha senso avere la classe implementare ICollection<T>. Alcuni dei membri di questa interfaccia sono abbastanza facili da implementare; per esempio, Count ritorna this.Values.Count.

Ma attuazione ICollection<T> richiede me per implementare IEnumerable<T> nonché IEnumerable (non generica), che è un po 'difficile per una raccolta thread-safe.

Naturalmente, potrei sempre gettare un NotSupportedException su IEnumerable<T>.GetEnumerator e IEnumerable.GetEnumerator, ma che si sente come un poliziotto-out per me.

Ho già una funzione getValues thread-safe che blocca su Values e restituisce una copia in forma di una matrice T[]. Quindi la mia idea era quella di implementare GetEnumerator restituendo this.getValues().GetEnumerator() in modo che il codice seguente sarebbe in realtà essere thread-safe:

ThreadSafeCollection coll = new ThreadSafeCollection ();

// add some items to coll

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

Purtroppo questa implementazione sembra funzionare solo per IEnumerable.GetEnumerator, non la versione generica (e quindi il codice di cui sopra genera un InvalidCastException).

Un'idea che avevo, che sembrava funzionare, è stato quello di lanciare il valore restituito da T[] getValues a un IEnumerable<T> prima di chiamare GetEnumerator su di esso. Un'alternativa sarebbe quella di cambiare getValues per restituire un IEnumerable<T> in primo luogo, ma poi per il IEnumerable.GetEnumerator non generico semplicemente cast del valore restituito da getValues ad un IEnumerable non generica. Ma non posso decidere se questi approcci si sentono sciatta o perfettamente accettabile.

In ogni caso, qualcuno ha una migliore idea di come andare a fare questo? Ho sentito parlare di metodi .Synchronized, ma sembrano essere disponibile per le collezioni non generici nello spazio dei nomi System.Collections unica. Forse c'è una variante generica di questo già esistente in .NET che ho semplicemente non so?

È stato utile?

Soluzione

La maggior parte delle collezioni specificano che non è possibile aggiungere o rimuovere le cose dalla raccolta durante l'iterazione. Ne consegue che per una raccolta thread-safe, si desidera bloccare altri thread di modifica della collezione mentre ogni thread è iterazione. Questo dovrebbe essere facile da fare con la sintassi iteratori, e non richiede di fare 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;
}

Ciò presuppone che tutte le altre operazioni che potrebbero modificare la raccolta anche this.Values serratura. Fino a quando questo è vero, si dovrebbe andare bene.

Altri suggerimenti

  

Purtroppo questa implementazione sembra funzionare solo per IEnumerable.GetEnumerator, non la versione generica (e quindi il codice di cui sopra genera InvalidCastException).

Sembra strano per me. Avete implementato non generico IEnumerable esplicitamente? Cioè hai scritto

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

Inoltre, hai provato a implementare IEnumerable con la sintassi iteratori. Dovrebbe essere facile:

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

Se hai provato, perché non si adatta alle vostre esigenze?

Il tipo T[] ha una realtionship speciale al tipo System.Array da cui deriva. Array come T[] sono state fatte prima che i generici sono stati introdotti nel .NET (altrimenti la sintassi avrebbe potuto essere Array<T>).

Il tipo System.Array ha una GetEnumerator() metodo istanza , public. Questo metodo restituisce IEnumerator non generico (dal .NET 1). E System.Array non ha implementazioni di interfacce esplicite per GetEnumerator(). Questo spiega le tue osservazioni.

Ma quando i generici sono stati introdotti in .NET 2.0, una speciale "hack" è stato fatto per avere un T[] implementare IList<T> e le sue interfacce di base (di cui IEnumerable<T> è uno). Per questo motivo penso che sia perfettamente ragionevole usare:

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

Nella versione di .NET dove sto controllando questo, le seguenti classi esistono:

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>
    {
    }
  }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top