2012-10-11 14 views
10

Mam następujące metody w klasie zewnętrznejIDictionary <,> Kontrawariancja?

public static void DoStuffWithAnimals(IDictionary<string, Animal> animals) 

W moim kodu wywołującego, mam już Dictionary<string, Lion> obiekt, ale nie mogę przekazać to jako argument w tej metodzie jest. Więc IDictionary<,> nie jest sprzeczny? Nie widzę żadnego powodu, dla którego to nie powinno działać.

Jedynym rozwiązaniem można myślę, jest:

var animals = new Dictionary<string, Animal>(); 

foreach(var kvp in lions) { 
    animals.Add(kvp.Key, kvp.Value); 
} 

Czy istnieje żaden sposób, aby zdać ten słownik w ten sposób, bez konieczności tworzenia nowego słownika tych samych obiektów?


EDIT:

Jak to jest moja metoda, wiem, że jedynym członkiem używam ze słownika jest getter od TValue this[TKey key], który jest członkiem IDictionary<TKey, TValue>, więc w tym scenariuszu , Nie mogę użyć parametru "szerszy" dla parametru.

+2

IDictionary wydaje się być niezmienna. Jego deklaracja nie zawiera szczegółów wejścia/wyjścia. –

Odpowiedz

5

Rozwiązanie mądry mógłby zrobić coś takiego, po prostu przekazać w akcesor Zamiast:

public static void DoStuffWithAnimals(Func<string, Animal> getAnimal) 
    { 
    } 

    var dicLions = new Dictionary<string, Lion>(); 
    DoStuffWithAnimals(s => dicLions[s]); 

Oczywiście, że może być trochę proste do swoich potrzeb, ale jeśli potrzebujesz tylko kilku metod słownikowych, łatwo jest je wprowadzić.

Jest to kolejny sposób, który daje trochę kodu ponownego wykorzystania między swoimi zwierzętami:

public class Accessor<T> : IAnimalAccessor where T : Animal 
    { 
     private readonly Dictionary<string, T> _dict; 

     public Accessor(Dictionary<string, T> dict) 
     { 
      _dict = dict; 
     } 

     public Animal GetItem(String key) 
     { 
      return _dict[key]; 
     } 
    } 

    public interface IAnimalAccessor 
    { 
     Animal GetItem(string key); 
    } 

    public static void DoStuffWithAnimals(IAnimalAccessor getAnimal) 
    { 
    } 

    var dicLions = new Dictionary<string, Lion>(); 
    var accessor = new Accessor<Lion>(dicLions); 
    DoStuffWithAnimals(accessor); 
+0

To pierwsze jest w porządku dla mojego przypadku, właśnie tego potrzebuję :) Odpowiedź na Laurence'a również działałaby w moim przypadku. – Connell

4

Załóżmy, że Derived jest podtypem Base. Następnie Dictionary<Base> nie może być podtypu Dictionary<Derived> ponieważ nie można umieścić dowolny obiekt typu Base w Dictionary<Derived>, ale Dictionary<Derived> nie może być podtypem Dictionary<Base> bo jeśli się obiektu z pomocą Dictionary<Base>, może nie być Derived. W związku z tym Dictionary nie jest ani współzależny, ani sprzeczny w swoim parametrze typu.

Ogólnie rzecz biorąc, kolekcje, na które można pisać, są niezmienne z tego powodu. Jeśli masz niezmienny zbiór, to może to być kowariancja. (Jeśli miałbyś jakąś kolekcję tylko do zapisu, to może być kontrawariantem.)

EDYCJA: A jeśli miałeś "kolekcję", z której nie można było uzyskać danych ani umieścić danych, to może to być zarówno współpraca, i contravariant. (Byłoby to również prawdopodobnie bezużyteczna.)

+2

Na przykład, 'DoStuffWithAnimals' może dodawać' Puppy' do 'animals', ale to powinno być nielegalne, jeśli' animals' jest zbiorem 'Lion' (chyba, że ​​lwy są głodne). – Brian

+0

Ach tak, oczywiście! W moim przypadku jest to tylko do odczytu, a metoda nie będzie dodawać żadnych szczeniąt do moich lwów. Jedynym członkiem, którego będzie używał jest getter z 'TValue to [klucz TKey]' (który jest członkiem 'IDictionary '.) Czy nadal nie ma obejścia? – Connell

2

Można modyfikować public static void DoStuffWithAnimals(IDictionary<string, Animal> animals) dodać rodzajowe ograniczenie. Oto coś, co działa na mnie w LINQPad:

void Main() 
{ 
    var lions = new Dictionary<string, Lion>(); 
    lions.Add("one", new Lion{Name="Ben"}); 
    AnimalManipulator.DoStuffWithAnimals(lions); 
} 

public class AnimalManipulator 
{ 
    public static void DoStuffWithAnimals<T>(IDictionary<string, T> animals) 
    where T : Animal 
    { 
     foreach (var kvp in animals) 
     { 
      kvp.Value.MakeNoise(); 
     } 
    } 
} 

public class Animal 
{ 
    public string Name {get;set;} 
    public virtual string MakeNoise() 
    { 
     return "?"; 
    } 
} 

public class Lion : Animal 
{ 
    public override string MakeNoise() 
    { 
     return "Roar"; 
    } 
} 
+0

Genialny pomysł! Dziękuję za to. +1 – Connell

0

Jeśli interfejs słownika została tylko do odczytu (pozwalając tylko do odczytu par klucz-wartość, a nawet nie może pozwolić możliwość ściągania wartość o jego klucz), można go oznaczyć za pomocą modyfikatora generycznego parametru out. Jeśli interfejs słownika był tylko do zapisu (pozwalając na wstawianie wartości, ale nie na ich pobieranie lub iterację), może być oznaczony ogólnym modyfikatorem parametru in.

Można więc utworzyć interfejs samodzielnie i rozszerzyć klasę Dictionary, aby uzyskać potrzebną funkcjonalność.

3

Po pierwsze, kowariancja i kontrawariancja w języku C# ma zastosowanie tylko do interfejsów i delegatów.

Więc twoje pytanie jest naprawdę o IDictionary<TKey,TValue>.

Z tym, że na uboczu, najprościej jest tylko pamiętać, że interfejs może być tylko w wariancie/contra-variant, jeśli wszystkie wartości parametru typu są tylko przekazywane lub tylko zanikają.

Na przykład (kowariancja):

interface IReceiver<in T> // note 'in' modifier 
{ 
    void Add(T item); 
    void Remove(T item); 
} 

I (kontrawariancja):

interface IGiver<out T> // note 'out' modifier 
{ 
    T Get(int index); 
    T RemoveAt(int index); 
} 

nigdy nie mogę zapamiętać, który z nich jest kowariantna i który jest kontrawariantny, ale ostatecznie nie ma znaczenia , o ile pamiętasz to rozróżnienie.

W przypadku parametru IDictionary<TKey,TValue> oba parametry typu są używane zarówno w funkcji wejściowej, jak i wyjściowej, co oznacza, że ​​interfejs nie może być kowariancyjny lub contravariant. Jest niezmienny.

Jednak klasa Dictionary<TKey,TValue> wykonuje implementację IEnumerable<T>, która jest kowariancyjna.

+0

To nie ma znaczenia w tym przypadku, ale współ- i kontrawariancji w C# dotyczy również delegatów. – svick

+0

Pozdrawiam @ Sick, zaktualizuję odpowiedź. –

+0

IReadOnlyDictionary może być wtedy kontrawariantny? – Slugart

2
  1. Wierzę, że to, co chcesz, nazywa się kowariancją, a nie kontrawariancją.
  2. Klasy w .Net nie mogą być współwystępujące, tylko interfejsy i delegaci.
  3. IDictionary<TKey, TValue> nie może być kowariantna, bo to pozwoli Ci zrobić coś takiego:

    IDictionary<string, Sheep> sheep = new Dictionary<string, Sheep>(); 
    IDictionary<string, Animal> animals = sheep; 
    animals.Add("another innocent sheep", new Wolf()); 
    
  4. Jest IReadOnlyDictionary<TKey, TValue> w .NET 4.5. Na pierwszy rzut oka może to być kowariancja (inny nowy interfejs, IReadOnlyList<T> jest kowariantny). Niestety, nie jest, ponieważ implementuje także IEnumerable<KeyValuePair<TKey, TValue>> i KeyValuePair<TKey, TValue> nie jest kowariantny.

  5. Aby obejść ten problem, można zrobić metoda rodzajowa z przymusu parametru typu:

    void DoStuffWithAnimals<T>(IDictionary<string, T> animals) where T : Animal 
    
+0

** 1. ** Kontrawariancja przechodzi z węższej na szerszą. Czy to nie jest to, co próbuję zrobić? ** 2. ** Ups. Wiedziałem, że zmieniłem pytanie, aby odnieść się do 'IDictionary', kiedy poprosiłem o tę część. ** 3. ** Aha, tak. Tego nauczyłem się z innych odpowiedzi. ** 4. ** Bugger. – Connell

+0

Re 1., to nie jest takie proste i myślę, że wyjaśnienie w Wikipedii jest mylące. [Cytując Eric Lippert:] (http://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx) "Rozważmy operację "Który manipuluje typami. Jeśli wyniki operacji zastosowane do dowolnego 'T' i' U' zawsze skutkują dwoma typami 'T'' i' U'' z tą samą relacją, co 'T' i' U', to operacja jest uważana za "Kowariant". Jeśli operacja odwraca wielkość i małość na jej wynikach [...], wówczas operacja jest uważana za "sprzeczną ze sobą". " – svick

Powiązane problemy