[.NET3.5,C#] Dynamic Casting

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
MarcellKehmstedt
Beiträge: 2
Registriert: 06.09.2012, 11:26

[.NET3.5,C#] Dynamic Casting

Beitrag von MarcellKehmstedt »

Guten Morgen,

ich habe hier ein Problem, an dem ich nun schon ein Weilchen herum doctore.
Und zwar habe ich eine Komponente, die über eine Property "Items" eine IList<object> übergeben bekommen soll.
Soweit, so gut. Dumm nur, dass es in .NET 3.5 noch keine kovarianten Generika gibt, so dass folgender Aufruf zu einem Fehler führt:

Code: Alles auswählen

IList<TestClass> dataList = new List<TestClass>();
Items = dataList;
Eine klare Lösung dafür wäre es, dataList als IList<object> zu deklarieren. Im gleichen Atemzug muss dann auch die Liste als Liste von objects angelegt werden.
Allerdings möchte ich das vermeiden, da ich schon ganz gerne wüsste, was sich so in der Liste befindet.

Die nächste Möglichkeit wäre es die Property mit Typ object zu deklarieren, so dass sich schließlich alles mögliche zuweisen lässt.
Dann bin ich aber in der Not innerhalb des zugehörigen Getters 1. zu prüfen, ob die übergebene Instanz die IList-Schnittstelle implementiert und 2. jene Instanz in eine solche IList zurück zu casten.

Die eigentliche Frage ist also wie ich ein "object" in eine "IList<T>" caste, wobei der generische Typ "T" (in diesem Fall ja "TestClass") noch zu bestimmen ist. Ich weiß, dass die Antwort Reflections lautet. Allerdings habe ich leider keine Ahnung wie sich dies damit umsetzen lässt.

Hat zufällig jemand eine Idee oder vllt. noch einen besseren Vorschlag?

Vielen Dank und beste Grüße,
Marcell
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [.NET3.5,C#] Dynamic Casting

Beitrag von CodingCat »

Auch in .NET 4 mit Unterstützung für ko-/kontravariante Generics ändert sich daran nichts. IList ist schlichtweg die falsche Schnittstelle, weil sie sowohl Schreib- als auch Lesezugriff zulässt. Um Typsicherheit zu garantieren, musst du dich schon vor dem Casten in die eine oder andere Richtung für Schreib- oder Lesezugriff entscheiden. So ist z.B. IEnumerable in .NET 4 kovariant. Tatsächlich ist die Schnittstelle das aber auch schon ein Stück weit in vorangegangenen Versionen, weil IEnumerable<T> von IEnumerable erbt.

Was das Property-Problem betrifft: Was genau stellt die Komponente denn mit der Object-Liste an? Ist es überhaupt legitim (d.h. sicher), eine nicht-Object-Liste zu übergeben? Sollte die Komponente ggf. einfach selbst ein Generic sein? Sollte die Komponente vielleicht eher ein IEnumerable entgegennehmen?
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
MarcellKehmstedt
Beiträge: 2
Registriert: 06.09.2012, 11:26

Re: [.NET3.5,C#] Dynamic Casting

Beitrag von MarcellKehmstedt »

Danke für die prompte Antwort. :)

Allerdings verstehe ich nicht, warum IList die falsche Schnittstelle sein soll. Letztlich stecken darin Objekte eines Typs und ich möchte eine Typumwandlung in eine Liste von Objekten eines Basistyps zu dem die ja per se kompatibel sind. In Java geht sowas seit Version 5.

Es wäre natürlich ein einfaches, wenn ich die Komponente selbst generisch machen würde. Allerdings darf ich das nicht, da es so Maßgabe ist. :/
Letztlich soll sich die Property Items so verhalten wie die verbreitete Property DataSource, welche ja zunächst mal auch jedes object annimmt und je nachdem, ob es sich um ein DataSet, eine Liste, etc. handelt verschiedenes Verhalten zeigt.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [.NET3.5,C#] Dynamic Casting

Beitrag von CodingCat »

In Java geht das meines Wissens nach nur für Arrays und ist, weil Arrays sowohl Lese- als auch Schreibzugriff erlauben, auch dort ein Loch im Typsystem, welches zu Abstürzen führen kann. Hier ein Beispiel für IList unter der Annahme, Casts in die eine oder andere Richtung seien erlaubt:

Code: Alles auswählen

// Annahme: Listen kovariant
IList<Data> dataList = new List<Data>();
IList<Object> objectList = dataList;

// Lesen in Ordnung (jedes Data-Objekt kann auch als Object-Objekt verwendet werden)
Object o = objectList[2];

// Schreiben wäre zur Compile-Zeit typkorrekt, aber ...
objectList[4] = new String();
// ... dataList enthält jetzt nicht mehr nur Data-kompatible Objekte,
// führt später mit großer Sicherheit zu Absturz

Code: Alles auswählen

// Annahme: Listen kontravariant
IList<Object> objectList  = new List<Object>();
IList<Data> dataList = objectList;

// Schreiben in Ordnung (jedes Data-Objekt ist auch ein Object-Objekt)
dataList[4] = new Data();

// Lesen wäre zur Compile-Zeit typkorrekt, aber tatsächlich könnten in der
// ursprünglichen Object-List alles andere als Data-Objekte stehen
Data d = dataList[2];
Wie zu sehen ist, kann für Schnittstellen mit Lese- und Schreibzugriff keine der beiden Vererbungsrichtungen zugelassen werden.
Es wäre natürlich ein einfaches, wenn ich die Komponente selbst generisch machen würde. Allerdings darf ich das nicht, da es so Maßgabe ist. :/
Letztlich soll sich die Property Items so verhalten wie die verbreitete Property DataSource, welche ja zunächst mal auch jedes object annimmt und je nachdem, ob es sich um ein DataSet, eine Liste, etc. handelt verschiedenes Verhalten zeigt.
Ja, das ist das Absurde, dass mit großem Aufwand sichere Typsysteme gebaut werden, nur um diese mit Reflection und Laufzeit-Typinformationen permanent zu unterwandern. Wenn die Beschränkung auf ein read-only Interface wie IEnumerable keine Option ist, bleibt dir wohl nichts anderes übrig, als nach dieser Maßgabe selbst das Typsystem in die Tonne zu treten. Dann ist es allerdings Blödsinn, dich überhaupt um Typen zu scheren. Bevor du also anfängst, mit Reflection Generics umzucasten, kannst du auch gleich direkt mit Objects arbeiten. (Ich hoffe, mein kleines Beispiel konnte dich davon überzeugen, dass ein Casten der Generics mindestens genauso unsicher ist wie das Arbeiten mit untypisierten Objects. Möglicherweise sogar noch unsicherer, weil die halbherzige Typisierung den falschen Anschein von Typkorrektheit erweckt.)
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
odenter
Establishment
Beiträge: 207
Registriert: 26.02.2009, 11:58

Re: [.NET3.5,C#] Dynamic Casting

Beitrag von odenter »

Falls Du das Objects mit der Items-Property nicht verändern kannst, wie wäre es mit folgendem:

Code: Alles auswählen

namespace ConsoleApplication2
{
  class Test
  {
    public string Name
    {
      get;
      set;
    }
  }

  class T2
  {
    private IList<object> _items;
    public IList<object> Items
    {
      get
      {
        if (_items == null)
        {
          _items = new List<object>();
        }
        return _items;
      }

      set
      {
        _items = value;
      }
    }
  }

  
  class Program
  {
    static void Main(string[] args)
    {
      T2 obj = new T2();
      obj.Items = (IList<object>)new List<Test>();
    }
  }
}
?

Falls Du es verändern kannst mache das Objekt mit der Items Property zu einem generischen Objekt, also wie folgt:

Code: Alles auswählen

  class T3<T>
  {
    private T _items = default(T);
    public T Items
    {
      get
      {
        return _items;
      }

      set
      {
        _items = value;
      }
    }
  }

    static void t()
    {
      T3<IList<Test>> obj = new T3<IList<Test>>();
      obj.Items = new List<Test>();
    }
oder

Code: Alles auswählen

class T4<T>
  {
    private IList<T> _items;
    public IList<T> Items
    {
      get
      {
        return _items;
      }

      set
      {
        _items = value;
      }
    }
  }

static void t4()
    {
      T4<Test> obj = new T4<Test>();
      obj.Items = new List<Test>();
    }
Da jede Klasse in .NET von System.object erbt kannst Du auch jede Klasse in System.object casten.
Antworten