Laden...

Problem beim Zugriff auf generische Klasse, die in Interface gepackt ist...

Erstellt von sth_Weird vor 6 Jahren Letzter Beitrag vor 6 Jahren 1.958 Views
S
sth_Weird Themenstarter:in
469 Beiträge seit 2007
vor 6 Jahren
Problem beim Zugriff auf generische Klasse, die in Interface gepackt ist...

Hallo,
ich habe ein Problem beim Zugriff auf eine generische Klasse, die ein Interface implementiert...
Ich bekomme von einer externen Quelle (auf die ich keinen Einfluss habe) ein Objekt vom Typ IMessage übergeben. Dieses enthält im konkreten Fall eine generische Implementierung von IMessage -> GenericMessage<T>. GenericMessage<T> enthält ein Property DataItem vom Typ T. Der Typ von T interessiert mich an dieser Stelle nicht, ich möchte einfach das, was in DataItem steht, in einer lokalen Variablen (vom Typ object) speichern und später weiterverarbeiten (sagen wir der Einfachheit halber mal, ich möchte das Objekt mit ToString in der Console ausgeben (angenommen ToString von T liefere informative Daten).
Aber wie komme ich an diese Information ran? Ich weiß, dass das Objekt, das mir als IMessage übergeben wird, vom Typ GenericMessage<T> ist, kenne aber den Typ von T nicht und möchte auch nicht alle möglichen bekannten Typen von T durchprobieren, ich möchte einfach nur an das DataItem Property kommen und das in einer lokalen Variablen vom Typ object speichern...

Gruß & Danke
sth_Weird

++++++++++++++++++++~+
Fluchen ist die einzige Sprache, die jeder Programmierer perfekt beherrscht


Linux is for free...if your time is worth nothing
++++++++++++++++++++~+

3.003 Beiträge seit 2006
vor 6 Jahren

Wenn es nicht per Reflection sein soll, müsstest du einen Basistypen einziehen, der DataItem abstrakt implementiert und von dem GenericMessage<T> erbt.

Der Irrtum bzw das Problem ist, anzunehmen, dass GenericMessage<T> ein Typ sei. Ist es nicht, deshalb kann man auch nicht auf GenericMessage<T> casten (sondern eben auf GenericMessage<int> usw., denn das sind Typen).

Wenn du die Möglichkeit einer abstrakten Basisklasse nicht hast - und es klingt so - solltest du es per Reflection in einer Extension lösen. (Auf propertyinfo "DataItem" testen und Wert extrahieren.)

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

2.078 Beiträge seit 2012
vor 6 Jahren

Wenn ich so ein Scenario baue, dann bekommt das nicht generische Interface für gewöhnlich die selbe Property vom Typ object und wird explizit implementiert.
Wenn das in deinem Fall nicht so ist und Du das auch nicht ändern kannst, dann wirst Du vermutlich nicht um Reflection herum kommen, da Du ja auch keinen Einfluss auf die Implementierung des Interfaces hast.

Bei Reflection brauchst Du dich aber nicht um den generischen Typ kümmern, da der schon fest steht und sich der Typ dann wie jeder andere Typ verhält.
Es reicht also, wenn Du die Message nach dem Message-Typ fragst, den dann nach einer Property mit dem Namen DataItem und darauf dann GetValue aufrufst.

object dataItem = message.GetType().GetProperty("DataItem").GetValue(message);

Noch ein bisschen Fehlerbehandlung drum herum, wenn z.B. die Property DataItem nicht existiert, dass Du das irgendwie behandeln kannst.

@LaTino:
Streng genommen ist ein GenericMessage<T> ein normaler Typ, zumindest behandelt .NET das als normalen Typ. An den kommst Du mit typeof(GenericMessage<>)
Du kannst auch alle Reflection-Operationen damit machen, aber nur so lange Du nicht "aktiv" mit den Membern arbeiten - z.B. Ausführen - musst.
Damit kannst Du so lange arbeiten, bis der konkrete generiche Parameter notwendig ist. Ausführen von Methoden geht also nicht, Definitionen abfragen aber schon.

Interessanterweise ist selbst das T ein normaler und eindeutiger Typ. Der beinhaltet das, was auch object kann plus das, was die Constraints vorschreiben.
An das T kommst Du, wenn Du über den generischen nicht konkreten Typ mit GetGenericArguments nach den Generischen Parametern fragst.
Du kannst auch vollständig damit arbeiten, wenn Du eine Instanz nimmst, die die Constraints erfüllt.

Das nur am Rande

3.003 Beiträge seit 2006
vor 6 Jahren

@LaTino:
Streng genommen ist ein GenericMessage<T> ein normaler Typ, zumindest behandelt .NET das als normalen Typ. An den kommst Du mit typeof(GenericMessage<>)

Das meinte ich nicht. Wären die Implementierungen von IMessage in der erhaltenen IEnumerable<IMessage> nicht generisch, dann könnte man bequem auf jede in der Liste enthaltene Instanz zugreifen, wenn man ihren Typ kennt.

Bei einer Implementierung von IMessage als generischer Typ geht das nicht, selbst wenn man weiß, welche Implementierung (hier: GenericMessage<>) benutzt wird: man muss schon den konkreten constructed type kennen. Das war mein Punkt. Generische Argumente kann man ausschließlich per Reflection oder per Selbstauskunft (zB über ein Property DataItem, das in einer Basisklasse oder dem Interface deklariert wird) erschließen.

Auf den generischen Typ kannst du eben nur casten, wenn du das generische Argument (und damit den constructed type) kennst. Ansonsten bleibt dir nur Reflection.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

2.078 Beiträge seit 2012
vor 6 Jahren

Dann hab ich dich falsch verstanden, sorry

Aber dann sind wir uns ja einig:
Ohne Reflection geht's nicht
Außer er hat Einfluss auf Interface und Implementierung, aber das hat er ja leider nicht.

3.003 Beiträge seit 2006
vor 6 Jahren

@sth_wired, so etwa:


interface IMessage { }

class GenericMessage<T> : IMessage
{
    public T DataItem { get; set; }
}

static class IMessageExtensions
{
    public static object GetDataItem(this IMessage message) => message.GetType().GetProperty("DataItem")?.GetValue(message);
}

void GetDataItemTest()
{
    var myList = new List<IMessage>
    {
        new GenericMessage<int> { DataItem = 5},
        new GenericMessage<string> { DataItem = "hello, world"},
        new GenericMessage<ConsoleColor> { DataItem = ConsoleColor.Black }
    };

    myList.Select(p => p.GetDataItem().ToString()).ToList().ForEach(Console.WriteLine);
}

@Paladin: reicht Zugriff auf Implementierung, s.o....ist egal, hat sie ja beides nicht 😉.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

S
sth_Weird Themenstarter:in
469 Beiträge seit 2007
vor 6 Jahren

Ja ich hatte auch schon befürchtet, dass ich um Reflection nicht rumkomme.

Gruß & Danke
sth_Weird

++++++++++++++++++++~+
Fluchen ist die einzige Sprache, die jeder Programmierer perfekt beherrscht


Linux is for free...if your time is worth nothing
++++++++++++++++++++~+