2012-10-11 16 views
26

Il mio problema è che ho un metodo che può prendere una raccolta come parametro che,IList <T> e IReadOnlyList <T>

  • ha una proprietà Count
  • ha un indicizzatore intero (get-only)

E non so quale tipo dovrebbe essere questo parametro. Vorrei scegliere IList<T> prima di .NET 4.5 poiché non c'era altra interfaccia di raccolta indicizzabile per questo e gli array lo implementano, il che è un grande vantaggio.

Ma .NET 4.5 introduce la nuova interfaccia IReadOnlyList<T> e voglio che anche il mio metodo lo supporti. Come posso scrivere questo metodo per supportare sia IList<T> e IReadOnlyList<T> senza violare i principi di base come DRY?

Posso convertire IList<T> in IReadOnlyList<T> in qualche modo in un sovraccarico?
Qual è la strada da percorrere?

Edit: la risposta di Daniel mi ha dato qualche idea, credo di poter andare con questo:

public void Foo<T>(IList<T> collection) 
{ 
    this.Foo(collection, collection.Count, i => collection[i]); 
} 

public void Foo<T>(IReadOnlyList<T> collection) 
{ 
    this.Foo(collection, collection.Count, i => collection[i]); 
} 

private void Foo<T>(
    IEnumerable<T> collection, 
    int count, 
    Func<int, T> indexer) 
{ 
    // Stuff 
} 

Edit 2: O ho potuto solo accettare un IReadOnlyList<T> e fornire un aiuto simile questo:

public static class CollectionEx 
{ 
    public static IReadOnlyList<T> AsReadOnly<T>(this IList<T> collection) 
    { 
     if (collection == null) 
      throw new ArgumentNullException("collection"); 

     // I now check whether it already is IReadOnlyList<T>... 
     // ...like casperOne has suggested. 
     return collection as IReadOnlyList<T> 
      ?? new ReadOnlyWrapper<T>(collection); 
    } 

    private sealed class ReadOnlyWrapper<T> : IReadOnlyList<T> 
    { 
     private readonly IList<T> source; 

     public int Count { get { return this.source.Count; } } 
     public T this[int index] { get { return this.source[index]; } } 

     public ReadOnlyWrapper(IList<T> source) { this.source = source; } 

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

Poi ho potuto chiamare Do(array.AsReadOnly())


Edit 3: Fun Fact: Array implementare sia IList<T>eIReadOnlyList<T>.

+0

Domanda correlata: http://stackoverflow.com/questions/12622539/why-doesnt-generic-icollection-implement-ireadonlycollection-in-net-4-5 –

risposta

19

Sei sfortunato qui. IList<T> non implementa IReadOnlyList<T>. List<T> implementa entrambe le interfacce, ma penso che non sia quello che vuoi.

Tuttavia, è possibile utilizzare LINQ:

  • Il metodo Count() estensione controlla internamente se l'istanza, infatti, è una raccolta e quindi utilizza la proprietà Count.
  • Il metodo di estensione ElementAt() controlla internamente se l'istanza di fatto è una lista e utilizza l'indicizzatore.
+1

Quindi la risposta cambierebbe il metodo in un modo che accetta un oggetto IEnumerable , che è supportato da qualsiasi oggetto che implementa ILIST o IReadOnlyList . –

+0

Se è importante per consentire solo 'IList ' e 'IReadOnlyList ' come input, mi creerebbe due overload pubbliche, uno per 'IList ' e una per 'IReadOnlyList ', e creare un sovraccarico privato con 'IEnumerable '. Altrimenti creerei solo una versione pubblica con 'IEnumerable '. –

+0

Grazie, ho deciso di non usare LINQ ma mi ha dato alcune idee. Ho aggiornato la domanda con la soluzione che userò. –

0

Dal IList<T> e IReadOnlyList<T> non condividono alcun "antenato" utile, e se non si desidera che il metodo per accettare qualsiasi altro tipo di parametro, l'unica cosa che puoi fare è fornire due overload.

Se si decide che il riutilizzo di codici è una priorità assoluta, allora si potrebbe avere questi sovraccarichi inoltrare la chiamata a un metodo che accetta privateIEnumerable<T> e usa LINQ nel modo Daniel suggerisce, in effetti lasciare LINQ fare la normalizzazione in fase di esecuzione.

Tuttavia, probabilmente sarebbe meglio copiare e incollare il codice una volta sola e mantenere solo due overload indipendenti che differiscono solo dal tipo di argomento; Non credo che la microarchitettura di questa scala offra qualcosa di tangibile, e d'altra parte richiede manovre non ovvie ed è più lenta.

+3

Non sono assolutamente d'accordo con copia/incolla, anche su questo livello. –

+2

@DanielHilgarth: Sono un credente nel "refactoring sul vedere una terza copia". Eliminare copia/incolla * incondizionatamente * in realtà non sembra una buona ingegneria. – Jon

+2

L'unica differenza è il tipo di input, tutto il resto è completamente uguale. Usando copia e incolla ti apri a bug sottili. Potresti correggere una versione ma dimenticare l'altra. Non vedo perché non sia una buona ingegneria prevenire qualcosa del genere sin dall'inizio. Qual è il vantaggio del tuo approccio per consentire due copie dello stesso codice? Qual è lo svantaggio del mio approccio a non farlo? –

4

Se siete più interessati a mantenere il principio di DRY rispetto alle prestazioni, si potrebbe usare dynamic, in questo modo:

public void Do<T>(IList<T> collection) 
{ 
    DoInternal(collection, collection.Count, i => collection[i]); 
} 
public void Do<T>(IReadOnlyList<T> collection) 
{ 
    DoInternal(collection, collection.Count, i => collection[i]); 
} 

private void DoInternal(dynamic collection, int count, Func<int, T> indexer) 
{ 
    // Get the count. 
    int count = collection.Count; 
} 

Tuttavia, non posso dire in buona fede che avevo consiglio come le insidie ​​sono troppo grandi:

  • Ogni chiamata sul collection in DoInternal saranno risolti in fase di esecuzione. Si perde controlli di sicurezza tipo, compilazione, ecc
  • degrado
  • Performance (anche se non grave, per il caso singolare, ma può essere aggregata) si verificano

tuo suggerimento helper è la più utile , ma penso che dovresti capovolgerlo; dato che il IReadOnlyList<T> interface è stato introdotto in .NET 4.5, molte API non hanno il supporto per questo, ma hanno il supporto per lo IList<T> interface.

Detto questo, è necessario creare un wrapper AsList, che accetta uno IReadOnlyList<T> e restituisce un wrapper in un'implementazione IList<T>.

Tuttavia, se si desidera sottolineare sulla propria API che si sta utilizzando uno IReadOnlyList<T> (per sottolineare il fatto che non si stanno modificando i dati), l'estensione AsReadOnlyList che si ha ora sarebbe più appropriata, ma io 'd fare la seguente ottimizzazione per AsReadOnly:

public static IReadOnlyList<T> AsReadOnly<T>(this IList<T> collection) 
{ 
    if (collection == null) 
     throw new ArgumentNullException("collection"); 

    // Type-sniff, no need to create a wrapper when collection 
    // is an IReadOnlyList<T> *already*. 
    IReadOnlyList<T> list = collection as IReadOnlyList<T>; 

    // If not null, return that. 
    if (list != null) return list; 

    // Wrap. 
    return new ReadOnlyWrapper<T>(collection); 
} 
+0

+1 per approfondimenti e grazie per il suggerimento , Sono andato con 'IList ' su 'IReadOnlyList ' perché mi sembrava sbagliato lanciare 'NotSupportedException's da interfacce esplicitamente implementate e, con mia sorpresa, accetta anche gli array. (È strano dal momento che 'Array' non lo implementa,' IReadOnlyList 'non ha nemmeno una versione non generica come' IList') Mi chiedo cosa ne pensi del mio primo campione però. Stavo pensando di usare 'dynamic', ma come hai detto, potrebbe danneggiare le prestazioni (chiamerò molto questo metodo in breve tempo) ... –

+0

... Quindi ho pensato che il' Count' non sarebbe mai cambiare (thread-safety non è inteso) e chiamare l'accessore get dell'indicizzatore usando un delegato sarebbe quasi veloce come chiamarlo direttamente. Quindi suppongo che la differenza di rendimento in questi approcci sia tra la creazione di un'istanza di 'ReadOnlyWrapper ' e la creazione di un'istanza di 'Func ' ogni volta che viene chiamata (e suona come micro-ottimizzazione). –

+0

@ ŞafakGür Arrays implementa 'IList ', quindi è probabilmente quello che stai vedendo. Ho scelto di andare dall'altra parte a causa dei problemi di interoperabilità con codice precedentemente esistente che non posso modificare. Per me, lanciare l'eccezione va bene, come afferma la prima riga della documentazione per tale eccezione (enfasi mia): "** L'eccezione che viene generata quando un metodo invocato non è supportato **". Sembra perfetto. Sì, è un'eccezione run-time, ma penso che stiamo parlando di casi d'uso limitati in cui stai interagendo con un'API che è stata progettata male in primo luogo ... – casperOne

1

Quello che vi serve è il IReadOnlyCollection<T> disponibile in .Net 4.5, che è essenzialmente un IEnumerable<T> che ha Count come la proprietà, ma se avete bisogno di indicizzazione e quindi è necessario che IReadOnlyList<T> darebbe anche un indicizzatore.

Non so voi ma penso che questa interfaccia sia un must che mancava da molto tempo.

+0

"Penso che questa interfaccia sia un must". È una classe, non un'interfaccia. Implementa sia 'IList ' che 'IReadOnlyList '. –

+0

Quello che volevo dire era IReadOnlyCollection In qualche modo mi mancava il "I" – MaYaN

+0

'IReadOnlyCollection ' ha lo stesso problema di 'IReadonlyList ': 'ICollection ' non lo implementa. – binki

Problemi correlati