Statische Funktionen abgeleiteter Klassen aufrufen

  • VB.NET

Es gibt 24 Antworten in diesem Thema. Der letzte Beitrag () ist von ~blaze~.

    Statische Funktionen abgeleiteter Klassen aufrufen

    Hallo.

    Ich stehe öfters vor dem Problem: Ich lese z.B. aus einer Datei Daten aus und erstelle daraus Objekte. Die Objekte haben alle einen gemeinsamen Basistyp.
    Beim Auslesen sieht das dann meistens so aus:

    VB.NET-Quellcode

    1. With BaseClass.TryParse(Source)
    2. If .Success Then
    3. Return .Result
    4. Else
    5. WasAnderesHalt()
    6. End If
    7. End With


    Da diese Daten bei mir meist im Format

    Quellcode

    1. Bezeichnung:Name1=Wert1;Name2=Wert2;...

    vorliegen, kann ich in der TryParse-Funktion einfach

    VB.NET-Quellcode

    1. Select Case DasVorDemDoppelpunkt
    2. Case "Schnitzel"
    3. Return Schnitzel.TryParse(DasNachDemDoppelpunkt)
    4. Case "Keks"
    5. Return Keks.TryParse...

    schreiben.
    Das funktioniert hier noch einigermaßen, da ich vor dem Aufrufen der Schnitzel.TryParse()-Funktion bereits weiß, dass es ein Schnitzel sein muss.
    Es hat aber hier schon den Nachteil, dass man alle Funktionen von Hand aufrufen muss.

    Bei meinem aktuellen Problem kann ich aber sowieso nicht vorher wissen, was es sein muss.
    Deshalb hätte ich vor, nacheinander alle TryParse()-Funktionen der abgeleiteten Klassen aufzurufen.

    Da habe ich im Moment folgendes im Kopf:

    VB.NET-Quellcode

    1. Public Class BaseClass
    2. Shared TryParseTargets As New List(Of Func(Of String, TryParseResult)) From {AddressOf Schnitzel.TryParse, AddressOf Keks.TryParse}
    3. Public Shared Function TryParse(Source As String) As TryParseResult
    4. For Each i In TryParseTargets
    5. Dim Result = i(Source)
    6. If Result.Accepted Then
    7. Return Result
    8. End If
    9. Next
    10. End Function
    11. End Class
    12. Public Class Schnitzel
    13. Public Shared Function TryParse(Source As String) As TryParseResult
    14. '...
    15. End Function
    16. End Class
    17. 'Und mit Keks auch


    Das ist bereits sehr kompakt, hat aber immer noch den Nachteil, dass man die Funktion vorher kennen muss.

    Es ist sicher möglich, die statischen Funktionen über Reflection aufzurufen (Über den Namen), aber das dürfte relativ langsam sein.

    Deshalb meine Frage:
    Gibt es einen performanten Weg, alle statischen XYZ-Funktionen aller abgeleiteter Klassen aufzurufen?



    Edit:

    Mir ist klar, dass man eine abgeleitete Klasse nicht dazu zwingen kann, eine statische Methode zu implementieren (wie Shared MustOverride Function).
    Deshalb ist es auch nicht möglich, sicher zu sagen, dass jede abgeleitete Klasse eine solche statische Funktion hat (bei mir ist es halt der Fall). Deshalb wird es kaum direkt möglich sein, da der Compiler nicht sicherstellen kann, dass es eine solche Funktion gibt.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „Niko Ortner“ ()

    Tja, Shared Klassenmember treten gewissermaßen aus dem objektorientierten Paradigma heraus, und daher kann man das OOP-Konzept "Vererbung" nicht für sich nutzen.

    Auf die eine oder andere Weise muß der Basisklasse die TryParse-Methoden der "Erben" bekannt gemacht werden, denn Überschreiben is nich.

    Wenn du statt Parsen befüllen könntest - da könnteman was frickeln.
    Weil bei einer Befüllung besteht das Objekt ja bereits, wenn die Werte eingefüllt werden, und da könnte die Befüll-Methode natürlich auch überschrieben werden.

    Ist ähnlich dem prinzipiellen Unterschied zwischen Dataset.Read/WriteXml und Xml-Serialisierung.
    Bin mir fast sicher, dass das ganze deutlich eleganter gehen müsste, aber:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    3. Dim cname As String = "ChildA"
    4. Dim ass As String = System.Reflection.Assembly.GetExecutingAssembly.GetName.Name
    5. Dim child = Type.GetType(ass & "." & cname)
    6. Dim mi = child.GetMethod("TryParse")
    7. Dim obj As Parent
    8. Dim o() As Object = {"foo", obj}
    9. mi.Invoke(Nothing, o)
    10. obj = o(1)
    11. Debug.Print(obj.ToString)
    12. End Sub
    13. End Class
    14. Public MustInherit Class Parent
    15. Public Shared Function TryParse(s As String, ByRef obj As Parent) As Boolean
    16. End Function
    17. End Class
    18. Public Class ChildA
    19. Inherits Parent
    20. Public Overloads Shared Function TryParse(s As String, ByRef obj As Parent) As Boolean
    21. obj = New ChildA
    22. Return True
    23. End Function
    24. Public Overrides Function ToString() As String
    25. Return "I am an A!"
    26. End Function
    27. End Class
    28. Public Class ChildB
    29. Inherits Parent
    30. Public Overloads Shared Function TryParse(s As String, ByRef obj As Parent) As Boolean
    31. obj = New ChildB
    32. Return True
    33. End Function
    34. Public Overrides Function ToString() As String
    35. Return "I am a B!"
    36. End Function
    37. End Class


    "Gehen" tut's jedenfalls ;)

    EDIT: Alternativ zb stackoverflow.com/questions/54…sses-of-an-abstract-class
    Eine Variante von picoflops Code, nur mit Dictionary als Zwischenspeicher, um die Performance zu erhöhen:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Delegate Function TryParseCall(s As String, ByRef result As BaseClass) As Boolean
    2. Public Class BaseClass
    3. Private Shared dict As New Dictionary(Of String, TryParseCall)
    4. Public Shared Function TryParse(s As String, ByRef result As BaseClass) As Boolean
    5. Dim t As String = s.Remove(s.IndexOf(":"c))
    6. If dict.ContainsKey(t) Then
    7. Return dict(t).Invoke(s.Substring(s.IndexOf(":"c) + 1), result)
    8. Else
    9. Select Case t
    10. Case "Keks"
    11. 'Das geht, weil Kovarianz bei Delegaten erlaubt ist!
    12. dict.Add(t, New TryParseCall(AddressOf Keks.TryParse))
    13. Case Else
    14. Return False
    15. End Select
    16. End If
    17. Return BaseClass.TryParse(s, result)
    18. End Function
    19. End Class
    20. Public Class Keks
    21. Inherits BaseClass
    22. Public Overloads Shared Function TryParse(s As String, ByRef result As Keks) As Boolean
    23. result = New Keks()
    24. Return True
    25. End Function
    26. End Class

    Zu Kovarianz siehe: msdn.microsoft.com/de-de/library/vstudio/ee207183.aspx

    Wenn du die Klassen vorher nicht kennst, bist du auf Reflection angewiesen. Dann solltest du das Dictionary im Voraus laden, um bei den Aufrufen keine zusätzliche Zeit zu verlieren. Ich kopiere mal das ganze Beispiel rein:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Reflection
    2. Module Module1
    3. Sub Main()
    4. Dim bc As BaseClassWithPreload = Nothing
    5. Dim result As Boolean = BaseClassWithPreload.TryParse("Keks:bla", bc)
    6. If result Then
    7. Console.WriteLine(bc.ToString()) 'KEKS!!!
    8. End If
    9. Console.ReadLine()
    10. End Sub
    11. End Module
    12. Public Class BaseClassWithPreload
    13. Private Shared dict As Dictionary(Of String, InvokeHelper)
    14. Shared Sub New()
    15. dict = New Dictionary(Of String, InvokeHelper)
    16. Dim types() As Type = System.Reflection.Assembly.GetExecutingAssembly.GetTypes()
    17. For Each t As Type In types
    18. Dim methods() As MethodInfo = t.GetMethods()
    19. For Each m As MethodInfo In methods
    20. If m.IsPublic AndAlso m.IsStatic AndAlso m.Name.Equals("TryParse") Then
    21. dict.Add(t.Name, New InvokeHelper(AddressOf m.Invoke))
    22. End If
    23. Next
    24. Next
    25. End Sub
    26. Public Shared Function TryParse(s As String, ByRef result As BaseClassWithPreload) As Boolean
    27. Dim t As String = s.Remove(s.IndexOf(":"c))
    28. If dict.ContainsKey(t) Then
    29. Return dict(t).Invoke(s.Substring(s.IndexOf(":"c) + 1), result)
    30. Else
    31. Return False
    32. End If
    33. End Function
    34. End Class
    35. Class InvokeHelper
    36. 'ACHTUNG: Diese Klasse sollte niemals Public sein, denn sie kann beliebigen Code im Kontext der Anwendung ausführen!
    37. 'Besser wäre es, sie als "Private Class" in BaseClassWithPreload zu integrieren.
    38. 'Außerdem sollte nie eine Instanz davon öffentlich "herumliegen"!
    39. Private _target As Func(Of Object, Object(), Object)
    40. Public Sub New(reflectionTarget As Func(Of Object, Object(), Object))
    41. _target = reflectionTarget
    42. End Sub
    43. Public Function Invoke(s As String, ByRef result As BaseClassWithPreload) As Boolean
    44. Dim tmp() As Object = {s, result}
    45. Dim ret As Object = _target.Invoke(Nothing, tmp)
    46. result = DirectCast(tmp(1), BaseClassWithPreload)
    47. Return DirectCast(ret, Boolean)
    48. End Function
    49. End Class
    50. Public Class Keks
    51. Inherits BaseClassWithPreload
    52. Public Overloads Shared Function TryParse(s As String, ByRef result As Keks) As Boolean
    53. result = New Keks()
    54. Return True
    55. End Function
    56. Public Overrides Function ToString() As String
    57. Return "KEKS!!!"
    58. End Function
    59. End Class

    Dieses Beispiel nutzt aus, dass die Reflection ByRef-Parameter in das Parameter-Array zurückkopiert, nachdem die aufgerufene Methode ausgeführt wurde.
    Gruß
    hal2000

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „hal2000“ ()

    Oh mann, das ist mir jetzt peinlich. Ich habe den Thread total vergessen.
    Tut mir leid deswegen.

    @ErfinderDesRades:
    Das Befüllen wäre bei mir eher nicht möglich. Ich komme dann nicht ganz über den Gedanken hinweg, dass ich dann erst wieder alles Objekte der abgeleiteten Klassen erstellen müsste, von denen die TryParse-Funktionen aufgerufen werden. Das it zwar möglich, aber dann muss ich mich erst wieder um die Liste kümmern.
    Also eine eher weniger optimale Lösung.

    @picoflop:
    Das sieht schon mal richtig gut aus.
    Da kann ich definitiv noch probieren.

    @hal2000:
    Das mit dem Dictionary hat mich auf eine Idee gebracht. Ich könnte eigentlich die MethodInfo- oder MethodBase-Objekte, die von der GetMethod()-Funktion zurückkommen in einer Liste speichern.

    Danke an alle.

    Ich stelle den Thread mal noch nicht auf Erledigt, da ich vielleicht noch die eine oder andere Frage zu Reflection habe.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    Niko Ortner schrieb:

    Ich komme dann nicht ganz über den Gedanken hinweg, dass ich dann erst wieder alles Objekte der abgeleiteten Klassen erstellen müsste,
    Das wäre eine sehr einfache Zeile Code:

    VB.NET-Quellcode

    1. Dim item As new AbgeleiteteKlasse


    von denen die TryParse-Funktionen aufgerufen werden.
    Ein Shared TryParse gäbe es dann nicht, sondern eine Objektmethode itm.TryFill(fillData) As Boolean (und die wäre Overridable).
    @ErfinderDesRades:
    Dim item As New AbgeleiteteKlasse

    Ach so. Das ist aus zwei Gründen schwierig:
    1. Muss ich dadurch vorher eine Instanz der abgeleiteten Klasse erstellen, was erfordert, dass ich weiß, dass sie existiert.
    Und dadurch ergibt sich
    2. Dass ich irgendwoher eine Liste bekommen muss, welche abgeleiteten Klassen es gibt.
    Das wäre auch über Reflection denkbar. Z.B.

    VB.NET-Quellcode

    1. 'Basisklasse
    2. Public MustOverride Function TryFill(Source As String) As TryFillResult
    3. 'Abgeleitete Klassen
    4. Public Overrides Function TryFill(Source As String) As TryFillResult
    5. '...
    6. End Function
    7. 'Beim Auslesen
    8. For Each i In GetAllKonstruktorenOfAbgeleiteteKlassenOfBasisKlasse()
    9. Dim Instance = DirectCast(i.Invoke(), BasisKlasse)
    10. Dim Result = Instance.TryFill(Source)
    11. If Result.Success Then
    12. Return Result
    13. End If
    14. Next


    Das gleicht schon sehr dem anderen Verfahren, das mir optisch einfach besser gefällt :)

    Aber die Idee ist gut. Das Prinzip kann ich vielleicht irgendwo gebrauchen.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Der Thread ist schon etwas alt, aber ich schreibe hier mal die Lösung rein, damit man auch darauf verlinken kann.

    Ich habe die wichtigen Stellen kommentiert.

    VB.NET-Quellcode

    1. Public Class Form1
    2. Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
    3. 'Ein Test:
    4. Dim SollteSchnitzelSein As Essen = Nothing
    5. Dim SollteKeksSein As Essen = Nothing
    6. If Essen.TryParse("Schnitzel:Saftig", SollteSchnitzelSein) Then
    7. MessageBox.Show(SollteSchnitzelSein.ToString)
    8. Else
    9. MessageBox.Show("Kein Schnitzel")
    10. End If
    11. If Essen.TryParse("Keks:Jaffa", SollteKeksSein) Then
    12. MessageBox.Show(SollteKeksSein.ToString)
    13. Else
    14. MessageBox.Show("Kein Keks")
    15. End If
    16. '*Ein Schnitzel: Saftig*
    17. '*Ein Keks: Jaffa*
    18. MyBase.OnLoad(e)
    19. End Sub
    20. End Class
    21. Public MustInherit Class Essen
    22. 'Ich mag es, wenn nur das deklariert ist, was man braucht.
    23. 'Weil nur über die Auflistung iteriert werden muss, reicht IEnumerable(Of ...) aus.
    24. Shared TryParseTargets As IEnumerable(Of System.Reflection.MethodBase)
    25. Shared Sub New()
    26. Dim TempList As New List(Of System.Reflection.MethodBase)
    27. 'Per Reflection werden alle Typen aus der aktuellen Assembly gelesen.
    28. For Each i In System.Reflection.Assembly.GetExecutingAssembly.GetTypes
    29. 'Davon werden die erbenden Typen gefiltert.
    30. If i.IsSubclassOf(GetType(Essen)) Then
    31. 'Die Funktion, die "I_TryParse" heißt und ein Argument vom Typ String entgegen nimmt (davon kann es nur eine geben) wird ausgelesen.
    32. Dim Method = i.GetMethod("I_TryParse", {GetType(String)})
    33. If Method Is Nothing Then
    34. 'Falls diese Funktion nicht existiert, wird hier eine Exception geworfen (Siehe Erklärung weiter unten im Post).
    35. Throw New System.Reflection.TargetException("Die Methode 'I_TryParse(ByVal Source As String) As TryParseResult' der abgeleiteten Klasse '" & i.Name & "' konnte nicht gefunden werden!")
    36. End If
    37. TempList.Add(Method)
    38. End If
    39. Next
    40. TryParseTargets = TempList
    41. End Sub
    42. Public Shared Function TryParse(ByVal Source As String, ByRef Obj As Essen) As Boolean
    43. 'Hier wird über alle ausgelesenen Funktionen iteriert.
    44. For Each i In TryParseTargets
    45. 'Invoken und ein bisschen casten.
    46. 'Der erste Parameter der Invoke-Funktion wäre bei Objektmethoden das Objekt (in der Methode dann "Me"). Bei statischen Methoden wird Nothing übergegeben.
    47. 'Der zweite Parameter ist ein Array, das die Parameter der Methode darstellt.
    48. With DirectCast(i.Invoke(Nothing, {Source}), TryParseResult)
    49. 'Die erste Funktion, die ein TryParseResult mit Success = True zurück gibt, hat das Essen erfolgreich geparst. (Das sollte normalerweise nur eine sein).
    50. If .Success Then
    51. Obj = .Value
    52. Return True
    53. End If
    54. End With
    55. Next
    56. 'Falls etwas ungültiges angegeben wurde wird False zurückgegeben und das ByRef-Objekt auf Nothing gesetzt
    57. Obj = Nothing
    58. Return False
    59. End Function
    60. 'Abgeleitete Klassen zum Testen.
    61. 'Der Konstruktor und die überschriebene ToString-Funktion sind nur da, um das Ganze vernünftig in der MessageBox anzeigen lassen zu können.
    62. Public Class Schnitzel
    63. Inherits Essen
    64. Dim Art As String
    65. Public Sub New(ByVal NewArt As String)
    66. Art = NewArt
    67. End Sub
    68. Public Overrides Function ToString() As String
    69. Return "Ein Schnitzel: " & Art
    70. End Function
    71. 'Das ist ein Beispiel für eine TryParse-Funktion. Man beachte die Konstruktoren der TryParseResult-Klasse.
    72. 'Das I_ Präfix steht hier für "Intern"... weil die Funktion nur intern verwendet wird.
    73. Public Shared Function I_TryParse(ByVal Source As String) As TryParseResult
    74. If Source.StartsWith("Schnitzel:") Then 'Nur als Beispiel
    75. Return New TryParseResult(New Schnitzel(Source.Substring(Source.IndexOf(":"c) + 1)))
    76. End If
    77. Return New TryParseResult
    78. End Function
    79. End Class
    80. 'Das selbe in grün... ähm braun.
    81. Public Class Keks
    82. Inherits Essen
    83. Dim Art As String
    84. Public Sub New(ByVal NewArt As String)
    85. Art = NewArt
    86. End Sub
    87. Public Overrides Function ToString() As String
    88. Return "Ein Keks: " & Art
    89. End Function
    90. Public Shared Function I_TryParse(ByVal Source As String) As TryParseResult
    91. If Source.StartsWith("Keks:") Then
    92. Return New TryParseResult(New Keks(Source.Substring(Source.IndexOf(":"c) + 1)))
    93. End If
    94. Return New TryParseResult
    95. End Function
    96. End Class
    97. 'Diese Klasse ist eigentlich ziemlich selbsterklärend.
    98. 'Die beiden Konstruktoren vereinfachen das Schreiben ein bisschen.
    99. Class TryParseResult
    100. Public ReadOnly Property Success As Boolean
    101. Get
    102. Return _Success
    103. End Get
    104. End Property
    105. Public ReadOnly Property Value As Essen
    106. Get
    107. Return _Value
    108. End Get
    109. End Property
    110. Dim _Success As Boolean
    111. Dim _Value As Essen
    112. Public Sub New()
    113. _Success = False
    114. _Value = Nothing
    115. End Sub
    116. Public Sub New(ByVal NewValue As Essen)
    117. _Success = True
    118. _Value = NewValue
    119. End Sub
    120. End Class
    121. End Class

    Im statischen Konstruktor der Essen-Klasse steht an einer Stelle "Siehe Erklärung weiter unten im Post".
    Das Problem ist hier, dass Visual Studio bei mir (2010 Express) nicht die Zeile mit dem Throw markiert, sondern die Zeile, bei der die Essen-Klasse zum ersten Mal verwendet wird.
    Da muss man dann in der InnerException nach der Message schauen. Wobei: Die Meldung "Der Typeninitialisierer für 'Projektname.Essen' hat eine Ausnahme verursacht." sollte schon genug Hinweis sein, weil man so eine Meldung sonst eigentlich nie bekommt. Sollte also nicht weiter schlimm sein.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Niko Ortner“ () aus folgendem Grund: Es ist keine TryParse-Funktion, wenn sie eine Exception wirft, wenn das Objekt nicht geparst werden konnte.

    Hi
    wenn du die MethodInfo in einen Delegaten kapselst (siehe hierzu [Delegate].CreateDelegate) fliegt keine InvocationException. Außerdem könnte es sein, dass er Aufruf dadurch leicht an Effizienz gewinnt. Bei Reflection ist das Problem, dass es keine sinngemäße Bindung ist, sondern eine namentliche. Klar, in den meisten Fällen wird TryParse das tun, was verlangt wird, aber es gibt keine Garantie dafür, daher ist das Aufrufen von "erreflektierten"-Methoden ohne Auflösung des sinngemäßen Kontexts (Interfaces usw.) nicht ratsam. Verwende stattdessen TypeConverter:

    VB.NET-Quellcode

    1. Public Shared Function TryParse(Of T)(ByVal str As String, ByRef value As T) As Boolean
    2. Try
    3. value = Parse(Of T)(str)
    4. Return True
    5. Catch ex As Exception
    6. value = Nothing
    7. Return False
    8. End Try
    9. End Function
    10. Public Shared Function Parse(Of T)(ByVal str As String) As T
    11. Dim converter As System.ComponentModel.TypeConverter = System.ComponentModel.TypeDescriptor.GetConverter(GetType(T))
    12. 'evtl. noch Culture uebergeben oder so
    13. Return DirectCast(converter.ConvertFromString(str), T)
    14. End Function

    Durch das System.ComponentModel.TypeConverterAttribute wird ein Attribut auf dem zu konvertierenden Typ spezifiziert, der einen System.ComponentModel.TypeConverter bereitstellt, der wiederum die Konvertierung von und zu anderen Typen bereitstellt, so also auch String.

    Nochmal: Es ist unbedingt wichtig, dass man sicher weiß, in welchem Kontext ein Aufruf steht. Da statische Methoden keine Instanzmethoden sind (hätte man so machen können), sind sie nicht "virtuell" und somit nicht überschreibbar, da nicht aus dem Typ einer Instanz gewählt wird, sondern aus der Deklaration des Typs. Reflection umgeht diese Regel, aber das ist äußerst unsauber, da, wie gesagt, keine Sicherheit besteht, dass die Methode dafür gedacht ist, selbst wenn ihre komplette Signatur identisch ist. Der Typ-Converter ist quasi ein Ausweg aus dem Problem, da er eben exakt spezifiziert, dass der verwiesene Typ speziell dafür implementiert wurde, Typkonversion durchzuführen.

    Gruß
    ~blaze~
    @~blaze~: Ich hab jetzt eine ganze Weile gebraucht, bis ich verstanden habe, was Du mir sagen willst ^^

    Ich habe mir, wärend ich den Beitrag geschrieben habe, auch gedacht, dass es mit Delegaten ein ganzes Stück schneller sein müsste. Aber ich hatte keine Idee, woher ich den Delegaten bekommen kann.
    Eine Idee hätte ich. Ich könnte, anstelle der MethodBase, die direkt die I_TryParse()-Funktion zeigt, Mit Reflection eine MethodBase holen, die auf eine Funktion zeigt, die einen Delegaten zurückgibt, der auf die I_TryParse()-Funktion zeigt. Das wäre dann beim Aufrufen der TryParse()-Funktion sicher schneller und funktionieren tut's auch (gerade getestet), aber wirklich sauber ist's noch nicht.

    Ich schau mir jetzt mal den TypeConverter an.

    Eine Frage zwischendurch: Wie kann der Compiler DirectCast(..., T) richtig auflösen? Er weiß ja nicht, was T für ein Typ ist, oder? Kompilieren lässt es sich.
    Interessant. Der Compiler lässt für Bla nur die Member zu, die auch Object hat:

    VB.NET-Quellcode

    1. Function Test(Of T)(ByVal Item As Object) As T
    2. Dim Bla As T = DirectCast(Item, T)
    3. End Function

    ... Aaah, ich verstehe. Bei "Of T As Essen" bekommt Bla alle Member von Essen.

    ...

    So, ich bin mit dem TypeConverter durch. Das funktioniert sogar. Man muss zwar für jede abgeleitete Klasse eine 9 Zeilen lange Klasse als Konverter implementieren (Was für ein Aufwand :D ), aber funktionieren tut's problemlos.

    So. Ich hoffe die Kommentare sind ausreichend. Es kann nicht schaden, wenn man vorher den Code in Post #8 gelesen hat, weil da die Kommentierung ausführlicher ist.

    VB.NET-Quellcode

    1. Public Class Form1
    2. Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
    3. 'Ein Test:
    4. Dim SollteSchnitzelSein As Essen = Nothing
    5. Dim SollteKeksSein As Essen = Nothing
    6. If Essen.TryParse("Schnitzel:Saftig", SollteSchnitzelSein) Then
    7. MessageBox.Show(SollteSchnitzelSein.ToString)
    8. Else
    9. MessageBox.Show("Kein Schnitzel")
    10. End If
    11. If Essen.TryParse("Keks:Jaffa", SollteKeksSein) Then
    12. MessageBox.Show(SollteKeksSein.ToString)
    13. Else
    14. MessageBox.Show("Kein Keks")
    15. End If
    16. '*Ein Schnitzel: Saftig*
    17. '*Ein Keks: Jaffa*
    18. MyBase.OnLoad(e)
    19. End Sub
    20. End Class
    21. Public MustInherit Class Essen
    22. Private Shared Converters As IEnumerable(Of System.ComponentModel.TypeConverter)
    23. Shared Sub New()
    24. Dim TempList As New List(Of System.ComponentModel.TypeConverter)
    25. 'Das Auslesen der Typen bleibt gleich. Irgendwo her müssen die ja kommen.
    26. For Each i In System.Reflection.Assembly.GetExecutingAssembly.GetTypes
    27. If i.IsSubclassOf(GetType(Essen)) Then
    28. 'Vom Typ wird der TypeConverter erstellt.
    29. Dim Converter = System.ComponentModel.TypeDescriptor.GetConverter(i)
    30. 'Falls der TypeConverter vergessen wurde, gibt diese Funktion False zurück.
    31. If Not Converter.CanConvertFrom(GetType(String)) Then
    32. Throw New NotSupportedException("Die abgeleitete Klasse '" & i.Name & "' unterstützt die benötigte Konvertierung von String nicht." & Environment.NewLine & _
    33. "Möglicherweise wurde vergessen den TypeConverter zu implementieren")
    34. End If
    35. TempList.Add(Converter)
    36. End If
    37. Next
    38. Converters = TempList
    39. End Sub
    40. Public Shared Function TryParse(ByVal Source As String, ByRef Obj As Essen) As Boolean
    41. For Each i In Converters
    42. 'Hier wird von String zu TryParseResult konvertiert. Dann muss noch gecastet werden.
    43. With DirectCast(i.ConvertFromString(Source), TryParseResult)
    44. If .Success Then
    45. Obj = .Value
    46. Return True
    47. End If
    48. End With
    49. Next
    50. Obj = Nothing
    51. Return False
    52. End Function
    53. 'Dieses Attribut gibt an, dass die Schnitzel-Klasse einen TypeConverter hat.
    54. 'Dieser TypeConverter ist der in der Klammer angegebene Typ. Also Schnitzel.SchnitzelConverter.
    55. <System.ComponentModel.TypeConverter(GetType(Schnitzel.SchnitzelConverter))> _
    56. Public Class Schnitzel
    57. Inherits Essen
    58. Dim Art As String
    59. Public Sub New(ByVal NewArt As String)
    60. Art = NewArt
    61. End Sub
    62. Public Overrides Function ToString() As String
    63. Return "Ein Schnitzel: " & Art
    64. End Function
    65. Public Shared Function I_TryParse(ByVal Source As String) As TryParseResult
    66. If Source.StartsWith("Schnitzel:") Then
    67. Return New TryParseResult(New Schnitzel(Source.Substring(Source.IndexOf(":"c) + 1)))
    68. End If
    69. Return New TryParseResult
    70. End Function
    71. 'Diese Klasse muss von System.ComponentModel.TypeConverter erben und die Funktionen CanConvertFrom und ConvertFrom überschreiben.
    72. 'Die ConvertTo-Funktionen können weg gelassen werden, weil wir ja nicht zu String konvertieren.
    73. Public Class SchnitzelConverter
    74. Inherits System.ComponentModel.TypeConverter
    75. Public Overrides Function CanConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal sourceType As System.Type) As Boolean
    76. 'Der Typ, von dem konvertiert werden muss, ist String. Wenn sourceType auch den String-Typ angibt, dann kann konvertiert werden.
    77. Return sourceType = GetType(String)
    78. End Function
    79. Public Overrides Function ConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object) As Object
    80. 'Hier wird die TryParse-Funktion aufgerufen.
    81. 'Das value-Argument ist vom Typ Object, deshalb muss gecastet werden.
    82. Return I_TryParse(DirectCast(value, String))
    83. End Function
    84. End Class
    85. End Class
    86. 'Das gleiche nochmal mit Keksen.
    87. <System.ComponentModel.TypeConverter(GetType(Keks.KeksConverter))> _
    88. Public Class Keks
    89. Inherits Essen
    90. Dim Art As String
    91. Public Sub New(ByVal NewArt As String)
    92. Art = NewArt
    93. End Sub
    94. Public Overrides Function ToString() As String
    95. Return "Ein Keks: " & Art
    96. End Function
    97. Public Shared Function I_TryParse(ByVal Source As String) As TryParseResult
    98. If Source.StartsWith("Keks:") Then
    99. Return New TryParseResult(New Keks(Source.Substring(Source.IndexOf(":"c) + 1)))
    100. End If
    101. Return New TryParseResult
    102. End Function
    103. Public Class KeksConverter
    104. Inherits System.ComponentModel.TypeConverter
    105. Public Overrides Function CanConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal sourceType As System.Type) As Boolean
    106. Return sourceType = GetType(String)
    107. End Function
    108. Public Overrides Function CanConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal destinationType As System.Type) As Boolean
    109. Return MyBase.CanConvertTo(context, destinationType)
    110. End Function
    111. Public Overrides Function ConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object) As Object
    112. Return I_TryParse(DirectCast(value, String))
    113. End Function
    114. Public Overrides Function ConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object, ByVal destinationType As System.Type) As Object
    115. Return MyBase.ConvertTo(context, culture, value, destinationType)
    116. End Function
    117. End Class
    118. End Class
    119. Public Class TryParseResult
    120. Public ReadOnly Property Success As Boolean
    121. Get
    122. Return _Success
    123. End Get
    124. End Property
    125. Public ReadOnly Property Value As Essen
    126. Get
    127. Return _Value
    128. End Get
    129. End Property
    130. Dim _Success As Boolean
    131. Dim _Value As Essen
    132. Public Sub New()
    133. _Success = False
    134. _Value = Nothing
    135. End Sub
    136. Public Sub New(ByVal NewValue As Essen)
    137. _Success = True
    138. _Value = NewValue
    139. End Sub
    140. End Class
    141. End Class


    Danke an ~Blaze~ für den Tipp!
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Niko Ortner“ ()

    Ein weiterer Vorteil ist, dass der TypeConverter auch vom PropertyGrid verwendet wird. Dann müsste aber auch ein ConvertToString-Äquivalent definiert werden.

    So könnte man's z.B. machen:

    VB.NET-Quellcode

    1. Private Shared Sub Examples()
    2. Test(Of Integer)("123")
    3. Test(Of Long)("123")
    4. Test(Of Single)("123,5")
    5. End Sub
    6. Private Shared Sub Test(Of T)(ByVal input As String)
    7. Dim value As T
    8. If GetParser(Of T)()(input, value) Then
    9. MessageBox.Show(value.ToString())
    10. End If
    11. End Sub
    12. Public Shared Function GetParser(Of T)() As TryParseHandler(Of T)
    13. Dim m As System.Reflection.MethodInfo = GetType(T).GetMethod("TryParse",
    14. Reflection.BindingFlags.Public Or Reflection.BindingFlags.Static,
    15. Nothing,
    16. New Type() {GetType(String),
    17. GetType(T).MakeByRefType},
    18. Nothing)
    19. Return DirectCast([Delegate].CreateDelegate(GetType(TryParseHandler(Of T)), m), TryParseHandler(Of T))
    20. End Function


    Übrigens wär's bei deinem Code praktisch, wenn du ein Dictionary, Hashset oder dergleichen nehmen würdest oder war das nur zur Veranschaulichung?

    Gruß
    ~blaze~
    Ich muss bei Deinem Code erst mal richtig durchblicken.
    If GetParser(Of T)()(input, value) Then
    So viele Klammern auf einem Haufen habe ich noch nie gesehen ^^


    Übrigens wär's bei deinem Code praktisch, wenn du ein Dictionary, Hashset oder dergleichen nehmen würdest oder war das nur zur Veranschaulichung?

    Was meinst Du damit? Mir fällt im Moment kein Verwendungszweck für ein Dictionary für diesen Zweck ein.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    VB.NET-Quellcode

    1. Public NotInheritable Class TypeConverterProvider
    2. Private Sub New()
    3. End Sub
    4. Private Shared _typeConverters As IDictionary(Of Type, System.ComponentModel.TypeConverter)
    5. Public Shared Function GetConverterSynchronized(ByVal type As Type) As System.ComponentModel.TypeConverter
    6. SyncLock _typeConverters
    7. Return GetConverter(type)
    8. End SyncLock
    9. End Function
    10. Public Shared Function GetConverter(ByVal type As Type) As System.ComponentModel.TypeConverter
    11. Dim tc As System.ComponentModel.TypeConverter = Nothing
    12. 'ist ein Dictionary vorhanden
    13. If _typeConverters Is Nothing Then
    14. _typeConverters = New Dictionary(Of Type, System.ComponentModel.TypeConverter)()
    15. 'Typeconverter fuer Typ hinzufuegen
    16. tc = System.ComponentModel.TypeDescriptor.GetConverter(type)
    17. _typeConverters.Add(type, tc)
    18. Else
    19. 'wenn noch kein Type-Converter definiert wurde, erzeugen und hinzufuegen, sonst zurueckgeben
    20. If Not _typeConverters.TryGetValue(type, tc) Then
    21. tc = System.ComponentModel.TypeDescriptor.GetConverter(type)
    22. _typeConverters.Add(type, tc)
    23. End If
    24. End If
    25. Return tc
    26. End Function
    27. End Class


    So zum Beispiel. Es wird immer dann ein TypeConverter erzeugt, sobald er zum ersten mal benötigt wird. Damit sind nicht alle Type-Converter drin (und nicht nur für eine bestimmte Menge von Typen). Bei einer hohen Anzahl an Type-Convertern könnte es allerdings sein, dass eine dynamische Generierung der Converter performanter ist (jenachdem, wie Type.GetType implementiert wurde). Ich glaub', dass die Metatoken der Typen Namenabhängig sind, bin mir aber nicht ganz sicher. Damit würde die Performance vmtl. zugunsten der erneuten Instanzierung ausfallen.

    Gruß
    ~blaze~
    Hm stimmt. Allerdings ist die Serialisierung unabhängig vom TypeConverter. Die Serialisierung wird durch mindestens 2 Möglichkeiten ermöglicht, wobei die Xml-Serialisierung noch mal eine Extrawurst ist. Ich kenn nur 2. Bei beiden wird das SerializableAttribute-Attribut auf den Typ gesetzt. Wenn dann der Typ noch ISerializable implementiert und einen Konstruktor mit der Signatur
    .ctr(System.Runtime.SerializationInfo, System.Runtime.Serialization.StreamingContext)
    enthält, wird die Serialisierung auf dieser Basis durchgeführt, ansonsten wird die SerializationInfo wohl durch Reflection generiert.
    Da gäb's jetzt noch die Möglichkeit, das in eine private Klasse zu kapseln, die den TypeConverter verwendet, um die Daten für die Serialisierung bereitzustellen. Insgesamt gibt's aber auch noch ein paar zusätzliche Konfigurationsmöglichkeiten.

    Edit: schade ;).

    Gruß
    ~blaze~
    Ach so. Danke für den Hinweis.
    Ich müsste es genauer testen.

    Konkret sieht es so aus, dass Quellcode verarbeitet wird. Es gibt für jeden Anweisungstyp (Assignment, CallStatement, SubDefinition, etc.) eine abgeleitete Klasse (insgesamt 14). Also gibt es 14 TypeConverter-Instanzen. Davon, denke ich, werden 10 in einer üblichen Codedatei immer vorkommen (keine geratene Zahl, sondern gezählt).
    Also denke ich, dass es keinen großen Performance-Schub gibt, wenn ich die Converter erst beim ersten Verwenden erstelle.

    Wobei: Dann wäre ein Dictionary (zumindest für mich) nicht verwendbar.
    Denn ich kenne den Typ vor dem Konvertieren nicht. Beim Beispiel oben könnte man anhand des Textes vor dem Doppelpunkt auf den Typen schließen. Bei mir ist das nicht möglich. Deshalb wird auch immer über alle Konverter iteriert und sobald der erste sagt "Hey, ich hab's erfolgreich konvertiert", wird das Ergenis der Konvertierung zurückgegeben. Das macht auch das dynamische instanzieren unnütz, weil beim ersten Durchlaufen der Liste alle Konverter bis zum erfolgreichen erstellt werden würden. Da wäre dann nur noch sehr wenig Performance rauszuholen. Ich denke, das wird sich nicht lohnen.

    Mir ist gerade eingefallen: Eine Sortierung nach der Wahrscheinlichkeit, wie häufig ein Converter passt, wäre sicher nicht schlecht. Liese sich über ein Attribut lösen.



    Edit: Mich würde auch interessieren, was @ErfinderDesRades: vorgeschlagen hat. Auch, wenn es nicht ganz gepasst hätte, wären sicher interessante Infos drin gewesen.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Ein einfaches Verfahren wäre auch einfach, eine verkettete Liste zu nehmen und das zuletzt verwendete Element an den Anfang zu stellen oder eine Liste, die aufsteigend nach Verwendungshäufigkeit sortiert. Damit wäre es nicht so statisch, da man ja teilweise nicht absehen kann, wie oft ein Element verwendet wird. Soll das eine Art Parser werden oder was genau? Da würde ich eher ein Dictionary nehmen und 1x beim ':' splitten oder aber aus der Überladung den Typ ermitteln. Reflection bietet ja für Methoden per GetParameters() die Parameter.

    Gruß
    ~blaze~
    Edit: Der Schnee hat gestern unser Internet lahmgelegt. Deshalb konnte ich nicht mehr antworten.


    Eine Art Parser, ja. Genaugenommen eine Weiterentwicklung von Was die Welt wirklich nicht braucht (Fun-Links, usw.)
    Ich lese Befehl für Befehl ein. Die vorhin genannten Klassen sind die hier:


    Da wird dann ein String wie "C = 1" übergeben, ein bisschen getestet, und dann kommt wenn's geklappt hat eine Instanz der Assignment-Klasse zurück (naja, eigentlich ein TryParseResult mit dem ParsePattern drin). In der ParsePatternBaseClass-Klasse ist ein IEnumerable(Of String) deklariert, das die einzelnen "Worte" beschreibt (in diesem Fall {"C", "=", "1"}) und das wird dann weiter verarbeitet (z.B. die Größe des zugewiesenen Wertes).


    Das mit der Liste habe ich mir mal angeschaut. Ich konnte es bisher noch nicht testen.
    Ich melde mich wieder, wenn ich Neuigkeiten habe.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Ich denke, das macht das ganze etwas komplizierter für dich, als es sein muss. ;)
    Ich würde bei einem Parser so vorgehen: Es wäre erst mal stark zu empfehlen, für einen Parser C# zu nehmen. Mit der Zeigerarithmetik musst du dich nicht so arg um die Indizierung kümmern und erhältst einen Performance-Schub. Ich gehe bei meinen Projekten immer davon aus, dass die Strings C-konform sind, d.h. sämtliche Strings enden mit einem '\0'-Char. Anschließend wird der String nach gewissen Grammatiken bearbeitet. Das gleiche geht übrigens auch in VB, nur musst du da entweder einen Enumerator oder einen Index nehmen (der Index ist praktischer, da du da die Ergebnisse nicht zwischenpuffern musst, sondern einfach Substring aufrufen kannst, wenn du einen Index vermerkst, aber C# ist find' ich definitiv besser als VB).

    C-Quellcode

    1. public class Parser
    2. {
    3. public static unsafe void Parse(string input)
    4. {
    5. fixed(char* inp = input)
    6. new Parser(inp).Parse();
    7. }
    8. }
    9. internal unsafe class ParserSurrogate
    10. {
    11. private readonly char* _bufferStart;
    12. private char* _current;
    13. public ParserSurrogate(char* input)
    14. {
    15. _bufferStart = _current = input;
    16. }
    17. public void Parse()
    18. {
    19. //...
    20. }
    21. }


    Das jetzt nur zur Veranschaulichung, falls du mit unsafe noch nichts gemacht hast. Alle Zeiger außer void* können inkrementiert, dekrementiert und subtrahiert, mit Offset addiert und subtrahiert werden. Die Subtraktion zweier Zeiger ergibt die Anzahl der Elemente, die zwischen den beiden Zeigern stehen, der Rest sollte klar sein.
    Statt dem Umweg über Type-Converter solltest du dir ein System überlegen, das deinem Parser zugrundeliegt. Ich kenne jetzt deine Überlegungen bzgl. Syntax nicht, aber angenommen, Schlüsselwörter beginnen immer mit einem Buchstaben, Zahlen sind dezimal und beginnen mit einer Dezimalziffer (vgl. 0x100L enthält auch Buchstaben), Ausdrücke können durch Klammern begrenzt werden und genügen z.B. folgender Regel:
    <expr> = (<expr>) | <expr> <binop> <expr> | <unop> <expr> | <string> | <char> | <number> | <expr> ? <expr> : <expr>
    und für die restlichen führst du halt sinngemäße Regeln ein. (Es ist übrigens einfacher, wenn du dir die Regeln nur so hinschreibst und nicht dann auf reguläre Ausdrücke zurückgreifst. Char bietet dir ja alle nötigen abfragen, abgesehen von Operatorzeichen und da kannst du dir ja einfach was zusammenbasteln).
    so musst du einfach die Regeln stupide abarbeiten und die einzelnen Ausdrücke herausfiltern. Wie du das dann machst, ist ja egal ;). Wenn du den string syntaktisch zerlegt hast, bildest du auf Basis einer abstrakten Klasse Expression einen Baum, der Schlüsselwörter usw. behandelt, den du dann in einem zusätzlichen Pass auflöst und in einen zweiten Baum überführst, der halt bereits der Ausführung nach geordnet ist (z.B. die Reihenfolge mehrerer binärer Operationen). Wenn das geschehen ist, löst du Verweise, Deklarationen, Funktionsaufrufe usw., was deine Sprache halt unterstützt auf und erzeugst einen zusätzlichen Baum (oder du machst es bei einer einfachen Sprache bereits im Schritt davor) und danach kannst du den Ausdruck auswerten lassen.

    Es geht zwar auch in einem einzigen Schritt, aber das ist dann extrem bescheuert zu warten. Mmn. kommt es hier nicht auf Performanz an und wenn man die Funktion mehrfach aufrufen will, übersetzt man sie halt, wenn man Interesse daran zeigt oder masochistisch veranlagt ist per System.Reflection.Emit.DynamicMethod und dem GetILGenerator in IL-Code oder erstellt ein paar Instruktionen, die man per Delegaten abrufen kann oder so. Muss man halt schauen, wie man das braucht ;). Für einen Parser ist es aber idR wohl ein wenig drüber hinausgeschossen, ein IL-Kompilat zu erzeugen, wenn es nicht unbedingt zeitkritisch sein muss.
    Jetzt noch schnell etwas zum konvertieren. Ich würde bei sowas immer auf die Typen der Funktionen Parameter und Rückgabewerte und Werte zurückgreifen. Z.B. numerische Ausdrücke, wie 42, 42.0f, 0x2a besitzen ja einen gewissen Typ. Wenn du dann Methodenaufrufe durchführst, so kannst du den Typ der Parameter aus diesen Werten bestimmen und entsprechen reagieren. Somit fallen eigentlich die TypeConverter bei einer richtigen Sprache weg. Wenn's nur eine Konsolensprache ist, kannst du stattdessen die TypeConverter hernehmen, um die Zeichenketten in den Parametern in die entsprechenden Äquivalente zu übersetzen. Da ist es aber definitiv einfacher, die erst herauszufiltern, usw.

    Hier hatte ich einen sehr einfachen, aber leicht zu verändernden hochgeladen, wenn er dich interessiert: [Suche] [kein Sold] kürzesten Java (oder VB, C#) Code zum Rechnen mit (+; -; *; /) *ohne* RegEx o. ä.
    Beachte aber auch die nachstehnden Postings.

    Ich hoffe mal, dass das kein Overkill ist. ;)

    Gruß
    ~blaze~
    Au au au! Ja, das ist etwas viel Information auf einmal ^^

    Also vom Prinzip her mache ich das schon irgendwie ähnlich.
    Der SourceStreamReader bringt den Quelltext in ein Leserliches Format. Das ist der Teil, den ich in C# machen könnte und ich denke, das werde ich auch. Ich muss mich da mal einlesen.
    Das schaut dann so aus wie bei einem StreamReader:

    VB.NET-Quellcode

    1. Dim Reader As New SourceStreamReader(Quelldatei) 'Die Quelldatei jetzt weiter zu erklären wäre übertrieben.
    2. Dim Sequence As SourceStreamReader.ReadResult = Nothing
    3. Do
    4. Sequence = Reaqder.ReadSequence
    5. If Sequence.Exists Then
    6. With PatternParsing.ParsePatternBaseClass.TryParse(Sequence.Value)
    7. '...
    8. End With
    9. End If
    10. Loop

    Egal wie man die Anweisung eingibt, solange es syntaktisch richtig ist, kommt das richtige heraus.
    C=1
    C = 1
    C = 1
    C = _
    1
    ergibt imm er C = 1.


    Ich habe gerade getestet.
    Die größte Datei die ich habe hat 17551 Zeichen, 1075 Zeilen und ergibt 939 ParsePattern-Instanzen.
    Das Ganze dauert ca 20ms auf dem neuen Laptop hier (Mehmals getestet).
    ... kurz auf dem alten Laptop testen...
    Da dauert es ca 45ms.
    Man beachte, dass ich an einigen Stellen logge (Die Datei wird aber nicht auf Platte geschrieben).
    ...
    Ich habe jetz nochmal alle Aufrufe an den Logger auskommentiert. Jetzt dauert's auf dem neuen Laptop ca. 9ms. Also ich hab da weitaus schlimmeres erwartet.

    ...
    Ich sehe gerade, dass ich hier die Optimierung mit den TypeConvertern noch gar nicht implementiert habe.
    Mal schauen...
    So. Jetzt dauerts's ca 7ms. Also viel optimierungspotenzial ist da nicht mehr :D


    Ich werde mir Deinen Post und den verlinkten Thread, sobald ich Zeit habe, in ruhe durchlesen.

    Vielen Dank für Deine ganzen Tipps und Infos!
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils