Laden...

C# Generics und IDictionary

Erstellt von heribert123 vor 13 Jahren Letzter Beitrag vor 13 Jahren 2.900 Views
H
heribert123 Themenstarter:in
11 Beiträge seit 2010
vor 13 Jahren
C# Generics und IDictionary

Hi!

Ich hab mal eine Frage. Ich würde gerne auf folgende Art "modularisieren":
Ich habe ein Generisches Interface welches mehrmals implementiert wird. Von allen soll eine Instanz in ein Dictionary als Value gelegt werden. Der Key des Dictionary ist vom Typ Type.


class Typ { /* [...] */ }
class EinTyp : Typ { /* [...] */ }
class AndererTyp : Typ { /* [...] */ }


interface MyInterface<T> : where T : Typ
{
    void DoSomething(T typ);
}

class MyImplFuerEinTyp : MyInterface<EinTyp> { /* [...] */ }
class MyImplFuerAndererTyp : MyInterface<AndererTyp> { /* [...] */ }


Auf diesen Beispiel aufbauend würde ich gerne folgendes Dictionary mache:


IDictionary<Type, MyInterface<Typ>> dic = new Dictionary<Type, MyInterface<Typ>>();

dic.Add(typeof(EinTyp), new MyImplFuerEinTyp());
dic.Add(typeof(AndererTyp), new MyImplFuerAndererTyp());

Damit ich später folgendes machen kann:


/* ... */
void IrgendeineMethode(Typ typ)
{
    /* ... */
    dic[typ.GetType()].DoSomething(typ);
    /* ... */
}

Die Alternative wäre natürlich ein Haufen abfragen mit dem is-Operator, will ich aber nicht. Ich hab keine Lust jedes mal ein

if (typ is EinTyp)
/*... */
else if (typ is AndererTyp)
/* ... */

zu machen.

Das Problem ist halt, dass ich ganz am Anfang nicht mal den Add auf das Dictionary durchführen kann, dass der Typ des Values nicht korrekt ist. Bei Java kann ich für solche Dinge über Wildcards bei Generics bei der Dictionary-Deklarition verwenden. Wildcard scheint es ja unter C# nicht zu geben, da die Generics in Cä nicht über Type-Erasure implementiert sind. Eigentlich müsste doch aber der Add klappen, da die hinzugefügten Values ja erben von den Typ, der das Dictionary erwartet.

Wie muss ich das am besten handhaben?

Besten Dank!

Gruß
Manuel

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

schau dir mal einen DI-Container (auch IoC-Container genannt) an. Wenn du das selber machen willst orientiere dich an einem bestehenden. Besser wäre aber einen fertigen und getesteten zu verwenden. Suche mal danach - es gibt viele.

ZB Building an IoC container in 15 lines of code

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo heribert123,

mal abgesehen von dem berechtigten Hinweis von gfoidl, müsste dein Vorhaben an sich funktionieren. Welche Fehlermeldung bekommst du an welcher Stelle?

herbivore

PS: Achso, ich glaube, jetzt habe ich verstanden, wo das Problem liegt, nämlich das MyInterface<EinTyp> kein Untertyp von MyInterface<Typ> ist, aber schreibt bitte trotzdem mal die genauen Fehlermeldungen.

PPS: Stichwort: Co- und Kontravarianz in C# 4.0 / .NET 4.0.

H
heribert123 Themenstarter:in
11 Beiträge seit 2010
vor 13 Jahren

Ich hab gesehen, dass ich ein Tippfehler in meinem vorherigen Beitrag hatte:
Es darf nicht

IDictionary<Type, MyInterface<Typ>> dic = new Dictionary<Type, MyInterface<Typ>>();

dic.Add(typeof(EinTyp), new MyImplFuerEinTyp());
dic.Add(typeof(EinTyp), new MyImplFuerAndererTyp()); // <-- Hier ist der Fehler

sondern muss

IDictionary<Type, MyInterface<Typ>> dic = new Dictionary<Type, MyInterface<Typ>>();

dic.Add(typeof(EinTyp), new MyImplFuerEinTyp());
dic.Add(typeof(AndererTyp), new MyImplFuerAndererTyp()); // <-- So muss es sein

Ich setze bereits Ninject als DI in dem Projekt ein. In diesem Zusammenhang halte ich DI nicht für das richtige Konzept und ist auch nicht möglich zu verwenden wie ich sehe. Oder gibt es bei Ninject wie bei dem Java-DI-Framework Guice Map- bzw. Multibindings

Es gibt folgende Fehlermeldung:

Error 2 'ConsoleApplication1.MyImplFuerAndererTyp' does not implement interface member 'ConsoleApplication1.MyInterface<ConsoleApplication1.AndererTyp>.DoSomething(ConsoleApplication1.AndererTyp)' \eigene dateien\visual studio 2010\Projects\ConsoleApplication1\ConsoleApplication1\Program.cs 19 7 ConsoleApplication1

Error 1 'ConsoleApplication1.MyImplFuerEinTyp' does not implement interface member 'ConsoleApplication1.MyInterface<ConsoleApplication1.EinTyp>.DoSomething(ConsoleApplication1.EinTyp)' \eigene dateien\visual studio 2010\Projects\ConsoleApplication1\ConsoleApplication1\Program.cs 18 7 ConsoleApplication1

Gruß

1.378 Beiträge seit 2006
vor 13 Jahren

Die Fehlermeldung sagt doch eindeutig, dass die Klassen

MyImplFuerEinTyp und
MyImplFuerAndererTyp

das interface

MyInterface<ConsoleApplication1.EinTyp> bzw. MyInterface<ConsoleApplication1.AndererTyp>

nicht richtig implementieren. Ich wüsst nicht wie man es Aussagekräftiger erklären könnte.

Lg XXX

H
heribert123 Themenstarter:in
11 Beiträge seit 2010
vor 13 Jahren

Upps, sorry, da hab ich Mist gebaut.

Wie du sicherlich gesehen hast, haben die die Fehlermeldungen nicht im Ansatz was mit meiner eigentlichen Problembeschreibung zu tun.

Die richten Fehlermeldungen sind:

Error 1 The best overloaded method match for 'System.Collections.Generic.IDictionary<System.Type,ConsoleApplication1.MyInterface <ConsoleApplication1.Typ>>.Add(System.Type, ConsoleApplication1.MyInterface<ConsoleApplication1.Typ>)' has some invalid arguments '\Eigene Dateien\Visual Studio 2010\Projects\ConsoleApplication1\ConsoleApplication1\Program.cs 48 13 ConsoleApplication1

Error 2 Argument 2: cannot convert from 'ConsoleApplication1.MyImplFuerEinTyp' to 'ConsoleApplication1.MyInterface <ConsoleApplication1.Typ>' \Eigene Dateien\Visual Studio 2010\Projects\ConsoleApplication1\ConsoleApplication1\Program.cs 48 37 ConsoleApplication1

Error 3 The best overloaded method match for 'System.Collections.Generic.IDictionary<System.Type,ConsoleApplication1.MyInterface <ConsoleApplication1.Typ>>.Add(System.Type, ConsoleApplication1.MyInterface<ConsoleApplication1.Typ>)' has some invalid arguments \Eigene Dateien\Visual Studio 2010\Projects\ConsoleApplication1\ConsoleApplication1\Program.cs 49 13 ConsoleApplication1

Error 4 Argument 2: cannot convert from 'ConsoleApplication1.MyImplFuerAndererTyp' to 'ConsoleApplication1.MyInterface <ConsoleApplication1.Typ>' \Eigene Dateien\Visual Studio 2010\Projects\ConsoleApplication1\ConsoleApplication1\Program.cs 49 41 ConsoleApplication1

1.378 Beiträge seit 2006
vor 13 Jahren

Hallo heribert123,

herbivore hat die Ursache für dein Problem schon genannt.

Eine Lösung dazu wäre evt. ein nicht generisches Interface einzuführen:


interface MyInterface
{
    void DoSomething(object typ);
}
interface MyInterface<T> : MyInterface where T : Typ
{
    void DoSomething(T typ);
}

und dieses dann im Dictionary zu verwenden.

Lg XXX

H
heribert123 Themenstarter:in
11 Beiträge seit 2010
vor 13 Jahren

@xxxprod:
Darauf bin ich auch selber schon gekommen. Diesen Lösungsansatz finde ich nicht wirklich praktikabel. Aber Danke für den Vorschlag.

@herbivore:
Mir ist nicht ganz klar wie mir hier Ko- bzw. Kontravarianz weiterhelfen kann.

Gruß

H
heribert123 Themenstarter:in
11 Beiträge seit 2010
vor 13 Jahren

Doch, ich versteh es. Das Problem ist, dass ich es unter .net 3.5 ausgeführt habe. Gibt es hierfür eine andere Lösung? Ich kann nicht .net 4.0 verwenden.

Danke!

Gruß

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo heribert123,

dann bleibt dir wohl nur, was xxxprod vorgeschlagen hat.

herbivore

1.361 Beiträge seit 2007
vor 13 Jahren

Hi,

Typsicherheit?
so wie heribert sich das vorstellt, ist es eh ungünstig.
Seine spätere Verwendung mittels

void IrgendeineMethode(Typ typ)
{
    /* ... */
    dic[typ.GetType()].DoSomething(typ);
    /* ... */
}

würde eh nur bei untypisierten Interfaces (wie das von xxxprod) funktionieren.
Falls dir das untypisierte Interface mangels statischer Typsicherheit nicht gefällt, beachte, dass du doch eh casten müsstest, denn "IrgendeineMethode" nimmt als Argument einen Typ-Wert. Die einzelnen Generischen-Implementations-Klassen nehmen aber bei dir immer nur Spezialisierungen an. Also wäre hier ein Cast notwendig, was die Typsicherheit ebenfalls wieder zerschießt. Zudem musst du auch manuell sicherstellen, dass zu jedem Typ auch ein Eintrag im Dict vorhanden ist.

Ko-/Kontravarianz
Und nicht einmal Ko-/Kontravarianz könnte dir hier helfen. Denn Kovarianz macht nur bei den Return-Werten Sinn und Kontravarianz bei den Parametern. Aber bei dir wird das Argument spezieller! Deshalb kannst du keine gemeinsame Basis finden. Das Substitutionsprinzip wird einfach nicht eingehalten.


void DoSomething(Typ)

ist nicht substituierbar durch


void DoSomething(SpeziellerTyp)

(andersrum wäre es richtig, und dann wäre es Kontravarianz beim Parameter)

Variante
Die einzig wirklich typsicherer Variante wäre das Speichern von "MyImplFuerEinTypX" direkt als (statischen) Member in der jeweiligen Klasse "TypX":

interface MyInterface<T> where T : Typ
{
    void DoSomething(T typ);
}

class MyImplFuerTestTyp : MyInterface<TestTyp> {
    public void DoSomething(TestTyp typ)
    {
        //...
    }
}

abstract class Typ
{
    public abstract void LetDoSomething();
}

class TestTyp : Typ
{
    public static MyInterface<TestTyp> CorrespondingImpl = new MyImplFuerTestTyp();
    public override void LetDoSomething() { CorrespondingImpl.DoSomething(this); }
}

Und Verwendung dann


void IrgendeineMethode(Typ typ)
{
    /* ... */
    typ.LetDoSomething();
    /* ... */
}

So ein bisschen wie der Double-Dispatch-Mechanismus.

Man hat natürlich immernoch den Initialisierungsaufwand jedem Typ seine Implementation zuzuweisen. Und natürlich muss man auch diesen Mechanismus bei jedem Typ-Ableiter einbauen.

Naja, irgendwo hat man immer seine Nachteile, bei separaten, aber parallelen Klassenhierarchien.

beste Grüße
zommi