Domanda

Prima ho nemmeno chiesto, mi permetta di ottenere la risposta ovvia fuori strada: L'interfaccia ICollection<T> include un metodo Remove per rimuovere un elemento arbitrario, che Queue<T> e Stack<T> non possono realmente supporto (dal momento che possono rimuovere solo elementi "end").

OK, mi rendo conto che. In realtà, la mia domanda non è specificamente sui tipi Queue<T> o raccolta Stack<T>; piuttosto, è la decisione di progettazione di mancata attuazione ICollection<T> per qualsiasi tipo generico che è essenzialmente una collezione di valori T.

Ecco quello che trovo strano. Di 'Ho un metodo che accetta una raccolta arbitraria di T, e ai fini del codice sto scrivendo Sarebbe utile conoscere la dimensione della collezione. Per esempio (il sottostante Codice è banale, solo per illustrazione!):

// Argument validation omitted for brevity.
static IEnumerable<T> FirstHalf<T>(this ICollection<T> source)
{
    int i = 0;
    foreach (T item in source)
    {
        yield return item;
        if ((++i) >= (source.Count / 2))
        {
            break;
        }
    }
}

Ora, non c'è davvero alcun motivo per cui il codice non può operare su un Queue<T> o un Stack<T>, salvo che questi tipi non implementano ICollection<T>. Essi do implementare ICollection, naturalmente, sto indovinando soprattutto per la proprietà Count da solo, ma che porta a ottimizzazione del codice strano come questo:

// OK, so to accommodate those bastard Queue<T> and Stack<T> types,
// we will just accept any IEnumerable<T>...
static IEnumerable<T> FirstHalf<T>(this IEnumerable<T> source)
{
    int count = CountQuickly<T>(source);
    /* ... */
}

// Then, assuming we've got a collection type with a Count property,
// we'll use that...
static int CountQuickly<T>(IEnumerable collection)
{
    // Note: I realize this is basically what Enumerable.Count already does
    // (minus the exception); I am just including it for clarity.
    var genericColl = collection as ICollection<T>;
    if (genericColl != null)
    {
        return genericColl.Count;
    }

    var nonGenericColl = collection as ICollection;
    if (nonGenericColl != null)
    {
        return nonGenericColl.Count;
    }

    // ...or else we'll just throw an exception, since this collection
    // can't be counted quickly.
    throw new ArgumentException("Cannot count this collection quickly!");
}

Non sarebbe più senso solo abbandonare completamente l'interfaccia ICollection (io lo faccio goccia non significa l'attuazione, naturalmente, come sarebbe una modifica sostanziale, ho solo media, fermata usando), e semplicemente implementare ICollection<T> con esplicito implementazione per i membri che non hanno una corrispondenza perfetta?

Voglio dire, guardare a ciò che offre ICollection<T>:

  • Count -. Queue<T> e Stack<T> entrambi hanno questo
  • IsReadOnly -. Queue<T> e Stack<T> facilmente potrebbero avere questo
  • Add -. Queue<T> potrebbe implementare questo in modo esplicito (con Enqueue), come potrebbe Stack<T> (con Push)
  • Clear - Controllare
  • .
  • Contains - Controllare
  • .
  • CopyTo - Controllare
  • .
  • GetEnumerator -. Check (stato derubato)
  • Remove -. Questo è l'unico che Queue<T> e Stack<T> non hanno un partner perfetto per

Ed ecco il kicker reale: ICollection<T>.Remove restituisce un bool ; così un'implementazione esplicita per Queue<T> potrebbe totalmente (per esempio) controllo se l'elemento da rimuovere è effettivamente l'elemento di testa (usando Peek), e, se così, chiamata Dequeue e true ritorno, altrimenti restituisce false. Stack<T> potrebbe facilmente essere data una realizzazione simile con Peek e Pop.

Bene, ora che ho scritto su di mille parole sul perché I che questo sarebbe possibile, mi pongo la domanda ovvia: perché non ha fatto i progettisti di Queue<T> e Stack<T> implementare questa interfaccia? Cioè, quali sono stati i fattori di progetto (che io probabilmente non considerando), che ha portato alla decisione che questa sarebbe stata la scelta sbagliata? Perché ICollection è stato implementato, invece?

Mi chiedo se, nel progettare il mio proprio i tipi, ci sono dei principi guida che dovrebbero prendere in considerazione rispetto alla implementazione di interfaccia che io possa essere vista in questa domanda. Ad esempio, è solo considerato cattiva pratica di implementare esplicitamente interfacce che non sono completamente supportate in generale (se è così, questo sembrerebbe conflitto con, per esempio, List<T> attuazione IList)? C'è una concettuale scollamento tra il concetto di una coda / stack e quello ICollection<T> vuole rappresentare?

Basically, ho la sensazione che ci deve essere una bella ragione buona Queue<T> (per esempio) non attuare ICollection<T>, e non voglio andare solo alla cieca in avanti a progettare il mio tipo e implementazione delle interfacce in un modo inappropriato senza essere informato e pienamente pensare attraverso quello che sto facendo.

Mi scuso per la domanda super-lungo.

È stato utile?

Soluzione

Non riesco a dare il "quello che era il pensiero vero e proprio" risposta - forse uno dei designer ci darà il reale thinkng e posso cancellare questo.

Tuttavia, mettendomi nella mentalità di "se qualcuno è venuto da me a prendere questa decisione", mi viene in mente una risposta .. Permettetemi di illustrare con questo codice:

public void SomeMethod<T>( ICollection<T> collection, T valueA, T valueB)
{

  collection.Add( valueA);
  collection.Add( valueB);

  if( someComplicatedCondition())
  {
    collection.Remove(valueA);
  }
}

(Certo, chiunque può creare una cattiva implementazione di ICollection<T>, ma ci aspettiamo il quadro per l'esempio). Supponiamo Stack / Queue implementare come dichiarate nella domanda. Così è il codice in alto a destra, o ha di casi bug bordo perché ICollection<T>.Remove() deve essere controllato? Se valueA DEVE essere rimosso, come posso risolvere questo lavoro sia con Stack e di coda? Ci sono risposte, ma ovviamente il codice di cui sopra è sbagliato in questo caso -. Anche se odora ragionevole

Quindi, entrambe le interpretazioni sono valide, ma sto bene con la decisione presa qui - se ho avuto il codice di cui sopra e sapevo di poter essere passata una coda o stack che ho potuto progettare intorno ad esso, ma sicuramente sarebbe un facile fossa bug a cadere in (in tutto il mondo che si vede ICollection<T>, ricordare i casi limite per rimuovere!)

Altri suggerimenti

In definitiva forse sono semplicemente non si adatta ideali; se si desidera un elenco o di raccolta - utilizzare un List<T> o Collection<T>; p

Re Add - v'è un presupposto implicito che Add aggiunge alla fine della collezione, che non è vero di una pila (anche se è per una coda). In un certo senso, in realtà mi confonde che l'enumeratore per lo stack / coda di non dequeue / pop, dal momento che gran parte aspetto elementi in una coda / pila da prelevare una volta (e solo una volta).

Forse anche (ancora una volta, usando il mio enumerator come solo un esempio) la gente semplicemente non poteva accordarsi su esattamente come dovrebbe comportarsi in alcuni di questi scenari, e manca un accordo completo, semplicemente non li attuazione è stata una scelta migliore.

Philip ha dato una buona risposta (+1). C'è un'altra promessa concettuale che Remove si romperà per Stack<T>. Il ICollection<T>.Remove è documentato come:

rimuove il prima occorrenza di un oggetto specifico da ICollection.

Stack<T> è LIFO, anche se Remove è stato implementato, sarà necessario rimuovere l'ultima occorrenza in caso di oggetti duplicati.

Se si pretende molto senso per Stack<T>, è meglio evitabile per il suo cugino uguale e contraria.


mi sarebbe piaciuto di più se MS:

  • non ha fatto documento Remove per ICollection<T> così. L'eliminazione di un qualche oggetto uguale avrebbe avuto più senso considerando quanto molto diverse le implementazioni interne di varie strutture sono. Forzare la rimozione dei primi sguardi voce come influenzata dalla ricerca lineare delle strutture semplici come array.

  • interfacce avuto per le strutture di coda. Può essere:

    public interface IPeekable<T> : IEnumerable<T> // or IInOut<T> or similar
    {
        int Count { get; }
    
        bool Contains(T item);
        T Peek();
        void Add(T item);
        bool Remove();
        void Clear();
    }
    
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top