"Static" Variable in statischer Methode in Structure nicht möglich

  • VB.NET

Es gibt 10 Antworten in diesem Thema. Der letzte Beitrag () ist von Niko Ortner.

    "Static" Variable in statischer Methode in Structure nicht möglich

    Bin heute zu der Erkenntnis gekommen, dass sowas in VB nicht möglich ist:

    VB.NET-Quellcode

    1. Public Structure Foo
    2. Public Shared Function Bar() As Integer
    3. Static Baz As Integer = 1
    4. Baz += 1
    5. Return Baz
    6. End Function
    7. End Structure

    Local-Variablen innerhalb von Methoden von Strukturen können nicht als "Static" deklariert werden.

    Dass das in Instanzmethoden in Structures nicht funktioniert hat gute Gründe, aber bei statischen Membern gibt es zwischen Klassen und Structures keinen Unterschied (der mir bekannt wäre).
    MSDN sagt zwar, dass es nicht erlaubt ist, aber der Grund wird nicht erklärt: msdn.microsoft.com/en-us/library/z2cty7t8.aspx

    Deshalb also die Frage: Warum ist es nicht erlaubt? Denn es würde trotzdem wie erwartet funktionieren. Oder übersehe ich da was?
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    @Niko Ortner Wäre dies äquivalent?

    VB.NET-Quellcode

    1. Public Structure Foo
    2. Private Shared Baz As Integer = 1
    3. Public Shared Function Bar() As Integer
    4. Baz += 1
    5. Return Baz
    6. End Function
    7. End Structure

    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    Facebamm schrieb:

    wäre mein Antwort und dann macht es kein Sinn etwas Static zu machen, weil es so oder so nur ein Kopie ist?


    In diesem Fall nicht, da es nur auf Instanzen von Strukturen zutrifft, nicht auf die statischen Elemente.
    Das von RodFromGermany sollte ein Äquivalent darstellen. Static wird auch nur zu einem Variablennamen aufgelöst. Der Vorteil ist, dass es nur im Scope der Methode vorhanden scheint.

    Viele Grüße
    ~blaze~
    @Facebamm
    Ich habe nicht ganz verstanden, was Du damit sagen wolltest. Beachte, dass es sich bei Bar um eine statische Methode handelt.

    @RodFromGermany
    Für einfache Fälle reicht das aus, aber es gibt ein paar Unterschiede. Der ausschlaggebende Unterschied ist für mich, dass der Ausdruck zur Initialisierung nur ausgewertet wird, wenn der Code tatsächlich ausgeführt wird.
    Beispiel:

    VB.NET-Quellcode

    1. Public Structure Foo1
    2. Private Shared Baz As Integer = CalculateMeaningOfLife()
    3. Public Shared Function Bar() As Integer
    4. Baz += 1
    5. Return Baz
    6. End Function
    7. End Structure
    8. Public Structure Foo2
    9. Public Shared Function Bar() As Integer
    10. Static Baz As Integer = CalculateMeaningOfLife()
    11. Baz += 1
    12. Return Baz
    13. End Function
    14. End Structure

    Bei Foo1 wird CalculateMeaningOfLife sofort ausgeführt (entsprechend der Regeln der CLR natürlich). Bei Foo2 wird CalculateMeaningOfLife garnie ausgeführt, wenn Foo2.Bar nie aufgerufen wird.
    Für Details, einfach ein Beispiel dekompilieren.


    @~blaze~
    Der Vorteil ist, dass es nur im Scope der Methode vorhanden scheint.

    Ja, das ist auch wichtiger Aspekt. Zwar in meinem konkreten Fall jetzt nicht so schlimm, aber es wäre trotzdem praktisch.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Im Prinzip handelt es sich immer nur um ein Singleton. Es muss ja trotzdem eine Abfrage drin sein, ob der Wert bereits einmal gesetzt wurde.

    VB.NET-Quellcode

    1. Class X
    2. Private Shared Baz As Integer
    3. Private Shared BazSet As Boolean
    4. Public Shared Function Bar() As Integer
    5. SyncLock GetType(X)
    6. If Not BazSet Then
    7. Baz = CalculateMeaningOfLife()
    8. End If
    9. End SyncLock
    10. Return Baz
    11. End Function
    12. End Class

    wäre eine Möglichkeit, denke ich. Das SyncLock GetType(X) ist allerdings hässlich (ich habe mir den IL-Code zu Static bisher nicht angeschaut und bin gerade zu faul). Da müsste man wohl noch ein Objekt einführen oder besser einen anderen Mechanismus benutzen, z.B. könnte man es mit einem spin lock auf BazSet versuchen:

    VB.NET-Quellcode

    1. Private Shared Baz As Integer
    2. Private Shared BazSet As Integer
    3. Public Shared Function Bar() As Integer
    4. Dim c As Integer = Interlocked.CompareExchange(BazSet, -1, 0)
    5. 'c = 0: Aktueller Thread berechnet Ergebnis, BazSet ist nach Ausführung des Vergleichs -1
    6. 'c = 1: Ergebnis wurde bereits berechnet und kann einfach zurückgegeben werden
    7. 'c = -1: Ergebnis wird gerade von einem anderen Thread berechnet
    8. If c = -1 Then
    9. 'Warten
    10. While BazSet <> 1
    11. Thread.Yield()
    12. End While
    13. ElseIf c = 0 Then
    14. 'Ergebnis berechnen und abspeichern
    15. Baz = CalculateMeaningOfLife()
    16. BazSet = 1
    17. Else
    18. 'Ergebnis einfach zurückgeben
    19. Return Baz
    20. End If
    21. End Function

    Ich habe allerdings keine Ahnung, wie performant das ist.
    Ggf. wäre es sinnvoll, sich genau anzuschauen, wie das im IL-Code gelöst wird.

    Viele Grüße
    ~blaze~
    Das ist genau der Anwendungsfall für Lazy<T>: msdn.microsoft.com/de-de/library/dd642331(v=vs.110).aspx
    Ist auch thread safe und meiner Meinung nach schöner als son Static gewurschtel, weil wirklich klar ist, dass das ganze nur initialisiert wird, wenn der Wert abgerufen wird.
    Mfg
    Vincent

    Vollständigkeitshalber:

    VB.NET-Quellcode

    1. Public Class Foo
    2. Public Shared Function Bar() As Integer
    3. Static Baz As Integer = CalculateMeaningOfLife()
    4. Baz += 1
    5. Return Baz
    6. End Function
    7. Private Shared Function CalculateMeaningOfLife() As Integer
    8. Return 42
    9. End Function
    10. End Class

    dekompiliert in etwa zu dem hier (Die Namen der Variablen sind vereinfacht, denn die sehen tatsächlich wie $STATIC$Bar$008$Baz$Init aus.):

    VB.NET-Quellcode

    1. Public Class Foo
    2. Private Shared Baz As Integer
    3. Private Shared BazInit As New StaticLocalInitFlag
    4. Public Shared Function Bar() As Integer
    5. Dim GotLock As Boolean = False
    6. Try
    7. Monitor.Enter(BazInit, GotLock)
    8. If BazInit.State = 0 Then
    9. BazInit.State = 2
    10. Baz = CalculateMeaningOfLife()
    11. ElseIf BazInit.State = 2 Then
    12. Throw New IncompleteInitialization()
    13. End If
    14. Finally
    15. BazInit.State = 1
    16. If GotLock Then
    17. Monitor.Exit(BazInit)
    18. End If
    19. End Try
    20. Baz += 1
    21. Return Baz
    22. End Function
    23. Private Shared Function CalculateMeaningOfLife() As Integer
    24. Return 42
    25. End Function
    26. End Class

    StaticLocalInitFlag kommt aus dem Microsoft.VisualBasic.CompilerServices Namespace und sieht einfach so aus:

    VB.NET-Quellcode

    1. Public Class StaticLocalInitFlag
    2. Public State As Int16
    3. End Class


    IncompleteInitialization ist übrigens die einzige Exception-Klasse die ich kenne, deren Name nicht auf Exception endet.

    Das Try-Finally sieht tatsächlich fast SyncLock BazInit, aber im Finally-Teil steht noch BazInit.State = 1.
    Also Du hast da schon den richtigen Riecher gehabt.

    Der Code lässt sich aber eins zu eins in eine Structure kopieren und er funktioniert dann immer noch, also erklärt das nicht, warum es der Compiler nicht zulässt.


    Edit:
    @VincentTB
    Du findest

    VB.NET-Quellcode

    1. Public Class Foo
    2. Private Shared Baz As New Lazy(Of Integer)(Function() CalculateMeaningOfLife())
    3. Public Shared Function Bar() As Integer
    4. Baz.Value += 1
    5. Return Baz.Value
    6. End Function
    7. Private Shared Function CalculateMeaningOfLife() As Integer
    8. Return 42
    9. End Function
    10. End Class
    schöner als

    VB.NET-Quellcode

    1. Public Class Foo
    2. Public Shared Function Bar() As Integer
    3. Static Baz As Integer = 1
    4. Baz += 1
    5. Return Baz
    6. End Function
    7. Private Shared Function CalculateMeaningOfLife() As Integer
    8. Return 42
    9. End Function
    10. End Class
    ?
    Hat Lazy<T>.Value überhaupt einen Setter? Nö, also da müsste man erst nochmal was dazuerfinden.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Sowas hatte ich bereits vermutet. Das SyncLock GetType(T) hätte man auf jeden Fall ersetzen müssen.
    Baz += 1 müsstest du übrigens durch Interlocked.Increment(Baz) ersetzen, da es sonst nicht threadsicher ist. Auch für Static.

    Ich denke, dass es nicht auf die Frage des Warums ankommt. Es ist einfach so, auch wenn es keinen Sinn macht.

    Viele Grüße
    ~blaze~
    Ich benutze Lazy relativ häufig, halt immer dann, wenn irgendetwas vielleicht gebraucht wird/vielleicht auch mehrmals. Tatsächlich hatte ich noch nie den Anwendungsfall, dass ich da noch irgendetwas ändern musste.

    Sonst kann man ja auch mal gucken, wie Lazy intern funktioniert/wie Microsoft das macht, wenn du wie @~blaze~ sowas selber schreiben willst: referencesource.microsoft.com/#mscorlib/system/Lazy.cs,348
    Mfg
    Vincent

    @~blaze~
    Ich denke, dass es nicht auf die Frage des Warums ankommt.

    Naja, als Mensch ist man halt immer auf der Suche nach Antworten ;)

    @VincentTB
    Nun, das ist aber kein Fall für Lazy. Lazy erlaubt nämlich nicht das Setzen der Value-Property, und genau das macht es zu einem Problem. Dann kann ich ja gleich wieder ein Boolean-Feld verwenden, das angibt, ob die Variable (bzw. halt das Feld) schon initialisiert wurde.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils