Casten von List(Of Object) nach List(irgendwas)?

  • VB.NET

Es gibt 2 Antworten in diesem Thema. Der letzte Beitrag () ist von Duster.

    Casten von List(Of Object) nach List(irgendwas)?

    Hallo :) ,

    ich hab ein kleines Problem. Ich habe eine Klasse gebaut der Listen (List(Of T)) verschiedener Typen übergeben werden z.B List(Of DashStyle),
    List(Of LineCap), List(Of String). Deshalb habe ich den Typ der Schnittstelle allgemeingehalten sodaß ich alles reinschieben kann nämlich
    List(Of Object).

    Ich schaffe es aber nicht den Typ List(Of Object) zur Weiterverarbeitung in einen entsprechenden anderen Typ List(Of DashStyle) etc.
    zu casten.
    Ich habs mit ConvertAll usw. probiert, kriege aber die Syntay einfach nicht richtig hin!

    Könnt Ihr mir da weiterhelfen und zeigen wie ich Listen richtig caste?

    ....so sieht meine Klasse aus:

    VB.NET-Quellcode

    1. Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
    2. Namespace Helpers
    3. Public Class ListServer
    4. #Region "Private Variables"
    5. #End Region
    6. #Region"Properties"
    7. Public Property GeneralList As New List(Of Object)
    8. #End Region
    9. #Region "Constructor"
    10. Sub New(someList As List(Of Object))
    11. GeneralList = someList
    12. End Sub
    13. #End Region
    14. Private Sub Test()
    15. Dim result As List(Of String)
    16. Dim testList As List(Of String)
    17. result = GeneralList. ?????????????????????????????????????????????????
    18. End Sub
    19. End Class
    20. End NameSpace



    UPDATE:

    Ich hab grad durch probieren herausgefunden dass ich gar nicht casten muss. Ich habe die Strings einfach der List(Of Object) zugewiesen. Aber nur weil es oberflächlich gesehen funktioniert heißt das noch lange nicht das das was ich hier gemacht hab "Best Practice" ist???? :?: .

    Ist das so wie ich es hier zsammengetackert habe gut und richtig oder gibt es eine ellegantere, effizientere und damit bessere Methode?

    Das reduzierte TestProjekt habe ich angehängt!

    Hier mein Source:

    VB.NET-Quellcode

    1. Option Strict On
    2. Public Class Form1
    3. Private Sub CmdCast_Click(sender As Object, e As EventArgs) Handles CmdCast.Click
    4. Dim testStrings() = {"Al", "Peg", "Kelly", "Bud", "Buck", "BUNDY"}
    5. Dim stringList As New List(Of String)
    6. Dim resultList As New List(Of String)
    7. Dim objectList As New List(Of Object)
    8. objectList.AddRange (testStrings)
    9. stringList.AddRange(testStrings)
    10. 'resultList =
    11. LsbTest.Items.AddRange (objectList.ToArray())
    12. End Sub
    13. End Class



    Vielen Dank für Eure Mühe vorab,
    Duster
    Dateien
    • ListenCasten.zip

      (228,04 kB, 46 mal heruntergeladen, zuletzt: )

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Duster“ ()

    Die architektonische Lösung: Warum musst du überhaupt casten? Wenn ListServer eine Liste von gewissen Dingen sein soll, dann sollte die Klasse vermutlich generisch sein:

    VB.NET-Quellcode

    1. Class ListServer(Of T)
    2. Public Property GeneralList As New List(Of T)
    3. End Class

    Dann kannst und willst du nur die Objekte reinschmeißen und rausholen, die du brauchst.


    Die pragmatische Lösung: Garnicht.


    Die ausführliche Lösung: Ein Ausdruck vom Typ List(Of Object) kann nie zu List(Of String) gecastet werden, und umgekehrt.
    Beispiel:

    VB.NET-Quellcode

    1. Sub PrintSomeStrings(StringItems As List(Of String))
    2. Dim Temp = ""
    3. For Each i In StringItems
    4. Temp &= i
    5. Next
    6. Console.WriteLine(Temp)
    7. End Sub
    8. Sub Test()
    9. Dim StringList As New List(Of String)
    10. StringList.Add("Hallo")
    11. StringList.Add("Welt")
    12. PrintSomeStrings(StringList) 'Funktioniert problemlos.
    13. Dim GeneralList As New List(Of String)
    14. GeneralList.Add("Hallo")
    15. GeneralList.Add(12345)
    16. GeneralList.Add(New Form)
    17. GeneralList.Add(DateTime.Now)
    18. PrintSomeStrings(GeneralList) 'Ups!
    19. End Sub

    Der zweite Aufruf an PrintSomeStrings wird nicht erlaubt. Denn die Liste kann auch Objekte beinhalten, die keine Strings sind. Die For-Each-Schleife in PrintSomeStrings verknüpft aber einen Haufen Strings. Der Code würde ganz unsinnige Sachen machen, wenn da plötzlich Objekte daherkommen würden, die keine Strings sind. Kann aber eh nicht passieren, denn der StringItems-Parameter ist vom Typ List(Of String), deshalb können auch nur Strings daherkommen.
    Es ist auch nie möglich, eine List(Of String) in einen Ausdruck vom Typ List(Of Object) zu bekommen, denn das gleiche Problem gibt es auch in die andere Richtung:

    VB.NET-Quellcode

    1. Sub PrintSomeStrings(StringItems As List(Of String))
    2. Dim Temp = ""
    3. For Each i In StringItems
    4. Temp &= i
    5. Next
    6. Console.WriteLine(Temp)
    7. End Sub
    8. Sub AddSomeObjects(GeneralItems As List(Of Object))
    9. GeneralItems.Add("Hallo")
    10. GeneralItems.Add(12345)
    11. GeneralItems.Add(New Form)
    12. GeneralItems.Add(DateTime.Now)
    13. End Sub
    14. Sub Test()
    15. Dim StringList As New List(Of String)
    16. AddSomeObjects(StringList) 'Ups!
    17. PrintSomeStrings(StringList) 'Funktioniert problemlos.
    18. Dim GeneralList As New List(Of String)
    19. AddSomeObjects(GeneralList) 'Funktioniert problemlos.
    20. PrintSomeStrings(GeneralList) 'Ups!
    21. End Sub

    Der erste Aufruf an AddSomeObjects wird nicht erlaubt, denn der Code fügt der Liste ja einen Haufen Objekte hinzu, die keine Strings sind. Das wäre ganz schön doof, wenn du Objekte, die keine Strings sind, in eine List(Of String) schmeißen könntest.

    Dieses Konzept nennt sich Invarianz. Also du hast einen generischen Typ, der in seinem Typenparameter invariant ist.
    Es gibt aber auch Varianz: Kovarianz und Kontravarianz.
    Ich kann mir aber nie merken, welches von den beiden jetzt welches ist, also sage ich hier stellvertretend Raus-Varianz und Rein-Varianz.

    Manchmal will man eine Funktion schreiben, die eine Liste entgegennimmt, wo Objekte eines gewissen Typs, oder Objekte, die von diesem Typ abgeleitet sind, rauskommen. Man will nie was reinlegen, sondern nur was rausnehmen. In .NET kann man sowas über Interfaces machen. List(Of T) implementiert IEnumerable(Of T), welches so deklariert ist: Interface IEnumerable(Of Out T). Beachte das Out beim generischen Typenparameter. Das bedeutet, dass dieses Interface einen Typ darstellt, aus dem Objekte dieses Typs rauskommen. Dementsprechend stellt ein Interface mit einem generischen In-Typenparameter einen Typ dar, in den man Objekte eines gewissen Typs reinschmeißen kann. (Dafür ist mir jetzt kein Typ bekannt, der so ein Interface implementiert.)

    Hier ein Beispiel (wie oben, aber umgedreht):
    Das IBucket-Interface ist von mir erfunden. Die List(Of T) implementiert es in .NET nicht wirklich, aber wir tun mal so, als wäre das der Fall, damit das Beispiel funktioniert.

    VB.NET-Quellcode

    1. Interface IBucket(Of In T)
    2. Sub Add(Item As T)
    3. End Interface
    4. Class List(Of T)
    5. Implements IEnumerable(Of T)
    6. Implements IBucket(Of T)
    7. '...
    8. End Class
    9. 'Diese Methode holt aus der angegebenen Liste beliebige Objekte raus und kann mit diesen beliebigen Objekten arbeiten. Auch mit Strings.
    10. Sub PrintSomeObjects(GeneralItems As IEnumerable(Of Object))
    11. Dim Temp = ""
    12. For Each i In GeneralItems
    13. Temp &= i.ToString()
    14. Next
    15. Console.WriteLine(Temp)
    16. End Sub
    17. 'Diese Methode holt aus der angegebenen Liste Strings raus und kann nur mit Strings arbeiten.
    18. Sub PrintSomeStrings(StringItems As List(Of String))
    19. Dim Temp = ""
    20. For Each i In StringItems
    21. Temp &= i
    22. Next
    23. Console.WriteLine(Temp)
    24. End Sub
    25. 'Diese Methode schmeißt in die angegebene Liste verschiedenste Objekte rein. Darunter auch Strings.
    26. Sub AddSomeObjects(GeneralItems As IBucket(Of Object))
    27. GeneralItems.Add("Hallo")
    28. GeneralItems.Add(12345)
    29. GeneralItems.Add(New Form)
    30. GeneralItems.Add(DateTime.Now)
    31. End Sub
    32. 'Diese Methode schmeißt in die angegebene Liste Strings rein, und zwar nur Strings.
    33. Sub AddSomeStrings(StringItems As IBucket(Of String))
    34. StringItems.Add("Hallo")
    35. StringItems.Add("Welt")
    36. StringItems.Add("Foo")
    37. StringItems.Add("Bar")
    38. StringItems.Add("Baz")
    39. End Sub
    40. Sub Test()
    41. Dim StringList As New List(Of String)
    42. AddSomeStrings(StringList) 'OK
    43. PrintSomeStrings(StringList) 'OK - Beides logisch. In eine List(Of String) werden Strings reingetan und rausgeholt.
    44. PrintSomeObjects(StringList) 'OK - Aus der List(Of String) kommen nur Strings raus. PrintSomeObjects kommt mit beliebigen Objekten klar, also auch mit Strings. Das ist Raus-Varianz. (Also halt eben Kovarianz oder Kontravarianz, aber ich weiß nicht mehr welches der zwei.)
    45. AddSomeObjects(StringList) 'Nope - In eine eine List(Of String) kann man nur Strings reingeben. AddSomeObjects versucht aber, auch andere Objekte reinzugeben.
    46. Dim GeneralList As New List(Of Object)
    47. AddSomeObjects(GeneralList) 'OK
    48. PrintSomeObjects(GeneralList) 'OK - Beides logisch. In eine List(Of Object) werden verschiedenste Objekte reingetan und rausgeholt.
    49. AddSomeStrings(GeneralList) 'OK - AddSomeStrings gibt nur Strings in die Liste rein. Eine List(Of Object) kommt mit beliebigen Objekten klar, also auch mit Strings. Das ist Rein-Varianz. (Also halt even Kontravarianz oder Kovarianz. Das jeweils andere von dem oben.)
    50. PrintSomeStrings(GeneralList) 'Nope - PrintSomeStrings kann nur Strings rausholen. Aus einer List(Of Object) können aber auch andere Objekte rauskommen.
    51. End Sub



    Die pragmatische Lösung V2: Aber was, wenn man eine List(Of Object) hat, von der man weiß, dass zur Laufzeit nur Strings drin sein werden? In diesem Fall kann man eine neue List(Of String) erstellen, jedes Element einzeln aus der List(Of Object) rausholen, zu String casten (was zur Laufzeit prüft, ob es tatsächlich ein String ist) und das dann in die neue List(Of String) tun. Das kann man entweder manuell machen, oder man verwendet die dafür vorgefertigte(n) LINQ-Funktion(en):

    VB.NET-Quellcode

    1. Sub Test()
    2. Dim GeneralList As New List(Of Object)
    3. AddSomeStrings(GeneralList)
    4. Dim StringList = GeneralList.Cast(Of String).ToList
    5. End Sub

    Wenn in der Liste zur Laufzeit ein Objekt vorkommt, das kein String ist (oder davon abgeleitet ist), dann wird eine InvalidCastException ausgelöst.
    Will man solche Objekte einfach auslassen, kann man stattdessen .OfType(Of String) verwenden.
    Aber beachte, dass bei der pragmatischen Lösung V2 die resultierende StringList nicht mehr die selbe ist, wie die GeneralList. Es ist eine neue Liste, die die selben Elemente beinhaltet. Rufst du also nachher StringList.Add("Baumkuchen") auf, wird der String nur zur neuen StringList hinzugefügt und die ursprüngliche ObjectList bleibt unverändert. Anders ist es nicht möglich.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils