2011-09-11 14 views
9

Mam problem z zwracaniem kolekcji i kowariancji i zastanawiałem się, czy ktoś ma lepsze rozwiązanie.Jak radzić sobie z kowariancją przy zwracaniu kolekcji wC#?

Scenariusz jest taki:

mam 2 wersje wykonania i chciałabym zachować wdrożenie wersji całkowicie oddzielne (chociaż mogą one mieć tę samą logikę). W implementacji chciałbym zwrócić listę elementów i dlatego w interfejsie zwrócę listę interfejsów elementu. Jednak w rzeczywistej implementacji interfejsu chciałbym zwrócić konkretny przedmiot tego przedmiotu. W kodzie wygląda to mniej więcej tak.

interface IItem 
{ 
    // some properties here 
} 

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

Następnie będą istnieć 2 przestrzenie nazw, które mają konkretną implementację tych interfejsów. Na przykład,

nazw 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>(); 
     } 
    } 
} 

Będzie kolejna realizacja tego samego pod namespace Version2.

Aby utworzyć te obiekty, powstanie fabryka, która weźmie wersję i odpowiednio utworzy odpowiedni rodzaj betonu.

Jeśli rozmówca wie dokładnie wersję i nie następuje, kod działa dobrze

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

Jednak chciałbym użytkownika, aby być w stanie zrobić coś takiego.

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

Ale, ponieważ został przekształcony do innej listy, dodatek nie będzie nic robić, ponieważ pozycja nie zostanie dodana z powrotem do oryginalnego obiektu wynikowego.

mogę wymyślić kilka rozwiązań, takich jak:

  1. mogę zsynchronizować dwie listy, ale to wydaje się być bardzo do zrobienia
  2. Return IEnumerable zamiast IList i dodać metodę tworzenia/usuwanie zbiory
  3. Tworzenie kolekcji niestandardowy, który bierze TConcrete i TInterface

rozumiem, dlaczego tak się dzieje (ze względu na bezpieczne i wszystkimi jej rodzaju), ale żaden z workaroun d Myślę, że może wydawać się bardzo elegancki. Czy ktoś ma lepsze rozwiązania lub sugestie?

Z góry dziękuję!

Aktualizacja

Po myśleć o tym więcej, myślę, że mogę wykonać następujące czynności:

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 
} 

potem mogę mieć

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; } 
    } 
} 

ten sposób, jeśli rozmówca jest dostęp do klasy Result, przejdzie przez CustomCollection.Add (element TConcrete), który jest już TConcrete.Jeśli wywołujący uzyskuje dostęp poprzez interfejs IResult, przejdzie on przez customCollection.Add (element TInterface), a walidacja nastąpi i upewnij się, że typ jest rzeczywiście TConcrete.

Spróbuję i zobaczę, czy to zadziała.

+1

Myślę, że przejście z opcją # 2 będzie wymagało najmniejszej ilości kodu. Spowoduje to również zmniejszenie powierzchni interfejsu, ponieważ implementacja nie będzie odpowiedzialna za pełne wsparcie IList, którego prawdopodobnie nie potrzebują. –

+0

Twoje rozwiązanie wygląda dobrze, ale dlaczego używasz 'ICustomCollection ', a nie 'ICollection ' bezpośrednio? – svick

+0

Całkowicie mogłem.Jedynym powodem jest to, że mam kilka metod, które są przeznaczone specjalnie dla kolekcjonowania, które nie są dostępne dla zwykłego ICollection , do którego należy uzyskać dostęp za pomocą kodu wewnętrznego, o którym zapomniałem wspomnieć. – Khronos

Odpowiedz

2

Problem, przed którym stoisz, polega na tym, że chcesz ujawnić typ, który mówi (między innymi) "możesz dodać do mnie dowolny IItem", ale to, co faktycznie zrobi, to "Możesz dodać tylko Item s" . Myślę, że najlepszym rozwiązaniem byłoby wyeksponowanie IList<Item>. Kod, który to wykorzystuje, musi wiedzieć o konkretnie Item, jeśli powinien dodać je do listy.

Ale jeśli naprawdę chcesz to zrobić, myślę, że najczystsze rozwiązanie to 3., jeśli dobrze cię rozumiem. To będzie opakowanie o numerze IList<TConcrete>, które zaimplementuje IList<TInterface>. Jeśli spróbujesz umieścić w nim coś, co nie jest TConcrete, rzuci wyjątek.

0

+1 do komentarza Brent: wróć do kolekcji bez modyfikacji i podaj metody modyfikacji na zajęciach.

Wystarczy przypomnieć, dlaczego nie można dostać 1 pracuje w sposób wytłumaczenia:

Jeśli spróbujesz dodać do List<Item> element, który po prostu implementuje IItem ale nie jest typu (lub pochodzącej z) element, który nie będzie w stanie zapisać ten nowy element na liście. Ponieważ zachowanie interfejsu wynikowego będzie bardzo niespójne - niektóre elementy implementujące IItem mogą zostać dodane poprawnie, sime zawiedzie, a kiedy zmienisz implementację na wersję 2, również będzie działać cahnge.

Prostą poprawką byłoby przechowywanie IList<IItem> insitead z List<Item>, ale natychmiastowe odsłonięcie kolekcji wymaga starannego myślenia.

0

Problem polega na tym, że Microsoft używa jednego interfejsu, IList <T>, aby opisać zarówno kolekcje, do których można dołączyć, jak i kolekcje, które nie mogą. O ile teoretycznie byłoby możliwe, aby klasa implementowała IList <Cat> w sposób zmienny, a także zaimplementowała IList <Animal> w niezmienny sposób (właściwość ReadOnly poprzedniego interfejsu zwróciłaby wartość false, a druga zwróciłaby wartość true), nie ma możliwości klasa określająca, że ​​implementuje ona IList <Cat> w jedną stronę, a IList <T> gdzie cat: T, w inny sposób. Życzę Microsoft uczynił IList <T> Lista <T> wdrożyć IReadableByIndex < z T >, IWritableByIndex < w T >, IReadWriteByIndex <T>, IAppendable < w T > i ICountable, ponieważ te umożliwiłyby kowariancji i kontrawariancji, ale nie zrobili tego. Pomocne może być samodzielne wdrożenie takich interfejsów i określenie dla nich opakowań, w zależności od tego, w jakim stopniu kowariancja byłaby przydatna.

Powiązane problemy