2011-09-11 8 views
9

Ho un problema con il ritorno di raccolta e covarianza e mi chiedevo se qualcuno ha una soluzione migliore.Come gestire la covarianza quando si restituisce la raccolta in C#?

Lo scenario è questo:

Ho 2 versione di attuazione e vorrei mantenere l'implementazione versione completamente separato (anche se può avere la stessa logica). Nell'implementazione, vorrei restituire un elenco di elementi e quindi nell'interfaccia, vorrei restituire un elenco di interfaccia dell'articolo. Tuttavia, nell'attuale implementazione dell'interfaccia, vorrei restituire l'oggetto concreto dell'oggetto. Nel codice, sembra qualcosa del genere.

interface IItem 
{ 
    // some properties here 
} 

interface IResult 
{ 
    IList<IItem> Items { get; } 
} 

Quindi, ci sarebbero 2 spazi dei nomi che ha un'implementazione concreta di queste interfacce. Ad esempio,

Namespace Version1

class Item : IItem 

class Result : IResult 
{ 
    public List<Item> Items 
    { 
     get { // get the list from somewhere } 
    } 

    IList<IItem> IResult.Items 
    { 
     get 
     { 
      // due to covariance, i have to convert it 
      return this.Items.ToList<IItem>(); 
     } 
    } 
} 

Ci sarà un'altra implementazione della stessa cosa sotto namespace Version2.

Per creare questi oggetti, ci sarà uno stabilimento che prende la versione e crea il tipo di calcestruzzo appropriato secondo necessità.

Se il chiamante conosce la versione esatta e fa seguito, il codice funziona bene

Version1.Result result = new Version1.Result(); 
result.Items.Add(//something); 

Tuttavia, vorrei che l'utente sia in grado di fare qualcosa di simile.

IResult result = // create from factory 
result.Items.Add(//something); 

Ma, perché è stato convertito in un altro elenco, il componente aggiuntivo non farà nulla perché l'elemento non verrà aggiunto di nuovo all'oggetto risultato originale.

mi viene in mente un paio di soluzioni come:

  1. ho potuto sincronizzare i due elenchi, ma che sembra essere un lavoro extra da fare
  2. ritorno IEnumerable invece di IList e aggiungere un metodo per creare/eliminare le raccolte
  3. creare una collezione personalizzata che prende il TConcrete e tInterfaccia

capisco perché questo sta accadendo (a causa di sicurezza e tutto il tipo), ma nessuno di workaroun Penso che possa sembrare molto elegante. Qualcuno ha soluzioni o suggerimenti migliori?

Grazie in anticipo!

Aggiornamento

Dopo pensando a questo di più, penso di poter effettuare le seguenti operazioni:

public interface ICustomCollection<TInterface> : ICollection<TInterface> 
{ 
} 

public class CustomCollection<TConcrete, TInterface> : ICustomCollection<TInterface> where TConcrete : class, TInterface 
{ 
    public void Add(TConcrete item) 
    { 
     // do add 
    } 

    void ICustomCollection<TInterface>.Add(TInterface item) 
    { 
     // validate that item is TConcrete and add to the collection. 
     // otherwise throw exception indicating that the add is not allowed due to incompatible type 
    } 

    // rest of the implementation 
} 

allora posso avere

interface IResult 
{ 
    ICustomCollection<IItem> Items { get; } 
} 

then for implementation, I will have 

class Result : IResult 
{ 
    public CustomCollection<Item, IItem> Items { get; } 

    ICustomCollection<TItem> IResult.Items 
    { 
     get { return this.Items; } 
    } 
} 

in questo modo, se il chiamante è accedendo alla classe Result, passerà attraverso CustomCollection.Add (TConcrete item) che è già TConcrete.Se il chiamante accede attraverso l'interfaccia IResult, passerà attraverso customCollection.Add (TInterface item) e la convalida avverrà e assicurerà che il tipo sia effettivamente TConcrete.

Farò una prova e vedere se questo funzionerebbe.

+1

Penso che andare con l'opzione # 2 richieda la minor quantità di codice. Ridurrà inoltre l'area della superficie dell'interfaccia, poiché l'implementazione non sarà responsabile del supporto completo di IList, che probabilmente i chiamanti non hanno bisogno. –

+0

La tua soluzione sembra buona, ma perché usi 'ICustomCollection ' e non 'ICollection ' direttamente? – svick

+0

Io totalmente potrei.L'unica ragione è che ho alcuni metodi specifici per la raccolta che non sono disponibili per la normale ICollection a cui è necessario accedere tramite codice interno, che ho dimenticato di menzionare. – Khronos

risposta

2

Il problema che si sta affrontando è perché si desidera esporre un tipo che dice (tra le altre cose) "è possibile aggiungere qualsiasi IItem a me", ma quello che effettivamente farà è "È possibile aggiungere solo Item s a me" . Penso che la soluzione migliore sarebbe quella di esporre in realtà IList<Item>. Il codice che utilizza questo dovrà conoscere il calcestruzzo Item in ogni caso, se dovesse aggiungerli alla lista.

Ma se davvero vuoi farlo, penso che la soluzione più pulita sarebbe 3., se ti capisco correttamente. Sarà un wrapper intorno a IList<TConcrete> e implementerà IList<TInterface>. Se provi a inserire qualcosa che non è TConcrete, genererà un'eccezione.

0

+1 al commento di Brent: restituisce la raccolta non modificabile e fornisce i metodi di modifica sulle classi.

solo ribadire il motivo per cui non è possibile ottenere uno che lavora in modo spiegabile:

Se si tenta di aggiungere a List<Item> elemento che implementa semplicemente IItem, ma non è di tipo (o derivato da) Articolo non sarà in grado di memorizzare questo nuovo elemento nell'elenco. Dato che il comportamento dell'interfaccia dei risultati sarà molto incoerente - alcuni elementi che implementano IItem possono essere aggiunti bene, sime fallirebbe, e quando cambierai l'implementazione in versione 2 lo farebbe anche.

La soluzione più semplice sarebbe archiviare IList<IItem> insitead di List<Item>, ma esporre direttamente la raccolta richiede un'attenta riflessione.

0

Il problema è che Microsoft utilizza un'interfaccia, IList <T>, per descrivere entrambe le raccolte alle quali è possibile aggiungere e raccolte che non possono. Mentre sarebbe teoricamente possibile per una classe implementare IList > Cat in modo mutabile e implementare anche IList <Animal> in modo immutabile (la proprietà ReadOnly dell'interfaccia precedente restituirebbe false, e quest'ultima restituirebbe true), non c'è modo per una classe per specificare che implementa IList <Cat> a senso unico e IList <T> dove cat: T, in un altro modo. Vorrei che Microsoft aveva fatto IList <T> Lista <T> attuare IReadableByIndex < out T >, IWritableByIndex < in T >, IReadWriteByIndex <T>, IAppendable < in T >, e ICountable, dal momento che coloro che avrebbero consentito di covarianza e controvarianza, ma non lo fecero. Potrebbe essere utile implementare tali interfacce manualmente e definire wrapper per loro, a seconda della misura in cui la covarianza sarebbe utile.

Problemi correlati