2012-10-11 17 views
10

Ich habe die folgende Methode in einer externen KlasseIDictionary <,> Kontravarianz?

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

In meinem Beruf Code, ich habe bereits ein Dictionary<string, Lion> Objekt, aber ich kann dies nicht als dieses Verfahrens des Argument übergibt in. Also eine IDictionary<,> ist nicht kontravariant? Ich kann keinen Grund sehen, warum das nicht funktionieren sollte.

Die einzige Lösung, die ich denken kann ist:

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

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

Gibt es keine Möglichkeit, dieses Wörterbuch in diese Methode zu übergeben, ohne ein neues Wörterbuch der gleichen Objekte erstellen zu müssen?


EDIT:

Da es meine Methode ist, weiß ich, dass das einzige Mitglied ich aus dem Wörterbuch verwendet habe, ist der Getter von TValue this[TKey key], die ein Mitglied von IDictionary<TKey, TValue> ist, so dass in diesem Szenario , Ich kann keinen 'breiteren' Typ für den Parameter verwenden.

+2

IDictionary scheint unveränderlich zu sein. Seine Deklaration enthält keine In/Out-Details. –

Antwort

5

Lösung weisen Sie so etwas tun könnten, in einem Accessor passiert nur statt:

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

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

Offensichtlich, dass wahrscheinlich ist, für Ihre Bedürfnisse ein bisschen einfach, aber Wenn Sie nur ein paar der Wörterbuch-Methoden benötigen, ist es ziemlich einfach, das an Ort und Stelle zu bekommen.

Dies ist eine weitere Möglichkeit, dass Sie ein Stück Code wiederverwendet zwischen Tieren gibt:

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

Das erste ist in Ordnung für meinen Fall, das ist genau das, was ich brauche :) @ Laurences Antwort würde auch in meinem Fall funktionieren. – Connell

4

Angenommen, Derived ist ein Untertyp von Base. Dann Dictionary<Base> kann kein Subtyp von Dictionary<Derived> sein, weil Sie kein Objekt vom Typ setzen können Base in ein Dictionary<Derived>, aber Dictionary<Derived> kann kein Subtyp von Dictionary<Base> sein, denn wenn Sie ein Objekt aus einem Dictionary<Base> bekommen, könnte es nicht sei ein Derived. Daher ist Dictionary in seinem Typparameter weder gleich- noch kontravariant.

Im Allgemeinen sind Kollektionen, aus denen Sie schreiben können, aus diesem Grund invariant. Wenn Sie eine unveränderbare Sammlung haben, könnte es kovariant sein. (Wenn Sie eine Art schreibgeschützte Sammlung hatten, könnte es kontravariant sein.)

EDIT: Und wenn Sie eine "Sammlung" hätten, von der Sie weder Daten erhalten noch Daten hineingeben könnten, dann könnte es beides sein. und kontravariant. (Es wäre wahrscheinlich auch nutzlos.)

+2

Zum Beispiel ist 'DoStuffWithAnimals' erlaubt,' '' '' Puppy'' hinzuzufügen, aber das sollte illegal sein, wenn 'animals' eine Sammlung von' Lion' ist (außer die Löwen sind hungrig). – Brian

+0

Ach ja, natürlich! Also in meinem Fall ist es schreibgeschützt und die Methode wird keine Welpen mit meinen Löwen hinzufügen. Das einzige Mitglied, das es benutzen wird, ist der Getter von 'TValue dieser [TKey Schlüssel]' (welcher ein Mitglied von IDictionary 'ist. Gibt es noch keine Workaround? – Connell

2

Sie public static void DoStuffWithAnimals(IDictionary<string, Animal> animals) eine generische Einschränkung hinzuzufügen, ändern könnte. Hier ist etwas, das für mich in LINQPad funktioniert:

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

Brilliante Idee! Dank dafür. +1 – Connell

0

Wenn die Schnittstelle des Wörterbuch wurde schreibgeschützt (so dass Sie nur die Schlüssel-Wert-Paare lesen, und konnte nicht einmal die Möglichkeit erlauben, einen Wert zu ziehen durch seinen Schlüssel), könnte er mit dem generischen Parametermodifikator out markiert werden. Wenn die Schnittstelle des Wörterverzeichnisses schreibgeschützt ist (Sie können Werte einfügen, aber nicht abrufen oder über sie iterieren), könnte sie mit dem allgemeinen Parametermodifikator in markiert werden.

So können Sie die Schnittstelle selbst erstellen und die Dictionary-Klasse erweitern, um die benötigte Funktionalität zu erhalten.

3

Erstens, Kovarianz und Kontravarianz in C# gelten nur für Schnittstellen und Delegaten.

So ist Ihre Frage wirklich über IDictionary<TKey,TValue>.

Damit ist es am einfachsten, sich daran zu erinnern, dass eine Schnittstelle nur dann co/kontravariant sein kann, wenn alle Werte eines Typparameters entweder nur übergeben oder nur weitergegeben werden.

Zum Beispiel (Kovarianz):

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

Und (Kontra):

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

Ich kann nie, welche davon erinnern ist covariant und das ist kontra, aber letztlich ist es egal, solange du dich an diese Unterscheidung erinnerst.

Im Fall von IDictionary<TKey,TValue> werden beide Typparameter sowohl in einer In- als auch in einer Out-Kapazität verwendet, was bedeutet, dass die Schnittstelle nicht kovariant oder kontravariant sein kann. Es ist invariant.

Die Klasse Dictionary<TKey,TValue> implementiert jedoch IEnumerable<T>, die kovariant ist.

+0

Dies ist in diesem Fall nicht relevant, aber die Ko- und Kontravarianz in C# gilt auch für Delegierte. – svick

+0

Cheers @svick, ich werde die Antwort aktualisieren. –

+0

IReadOnlyDictionary könnte dann kontravariant sein? – Slugart

2
  1. Ich glaube, was Sie wollen, heißt Kovarianz, nicht Kontravarianz.
  2. Klassen in .Net können nicht ko- oder kontravariant sein, nur Schnittstellen und Delegaten können.
  3. IDictionary<TKey, TValue> nicht covariant sein, denn, dass Sie so etwas wie zu tun erlauben würde:

    4,5
    IDictionary<string, Sheep> sheep = new Dictionary<string, Sheep>(); 
    IDictionary<string, Animal> animals = sheep; 
    animals.Add("another innocent sheep", new Wolf()); 
    
  4. Es gibt IReadOnlyDictionary<TKey, TValue> in .Net. Auf den ersten Blick könnte es kovariant sein (die andere neue Schnittstelle, IReadOnlyList<T> ist kovariant). Leider ist es nicht, weil es auch implementiert IEnumerable<KeyValuePair<TKey, TValue>> und KeyValuePair<TKey, TValue> ist nicht kovariant.

  5. Um dies zu umgehen, könnten Sie Ihre Methode generisch mit einer Einschränkung des Typs Parameter machen:

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

** 1. ** Contravariance konvertiert von schmaler zu breiter. Ist das nicht das, was ich versuche? ** 2. ** Whoops. Ich wusste, dass ich die Frage geändert habe, um auf IDictionary zu verweisen, als ich diesen Teil fragte. ** 3. ** Aha, ja. Das habe ich aus den anderen Antworten gelernt. ** 4. ** Bugger. – Connell

+0

Zu 1., es ist nicht so einfach und ich denke, die Erklärung bei Wikipedia ist verwirrend. [Zitat von Eric Lippert:] (http://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx) "Betrachten Sie eine" Operation "Welche Typen manipuliert. Wenn die Ergebnisse der Operation, die auf irgendein "T" und "U" angewendet werden, immer zu zwei Arten "T" und "U" mit der gleichen Beziehung wie "T" und "U" führen, wird gesagt, dass die Operation ist "Kovariante". Wenn die Operation Größe und Kleinheit auf ihre Ergebnisse umkehrt, [...] dann wird die Operation als "kontravariant" bezeichnet. " – svick

Verwandte Themen