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:
- mogę zsynchronizować dwie listy, ale to wydaje się być bardzo do zrobienia
- Return IEnumerable zamiast IList i dodać metodę tworzenia/usuwanie zbiory
- 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.
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ą. –
Twoje rozwiązanie wygląda dobrze, ale dlaczego używasz 'ICustomCollection', a nie 'ICollection ' bezpośrednio? –
svick
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