VB9 XML Literals


Ένα από τα νέα χαρακτηριστικά της VB.NET είναι η υποστήριξη XML Literals. Πρόκειται για τη δυνατότητα της ενσωματώσης XML μέσα στον κώδικα. Για παράδειγμα, μπορεί να πει κάποιος

Dim countriesWithCapital As XElement = _
    <Countries>
        <Country Name=’SomeCountry’ Density=’123456′> 
            <Capital>
                <Name>City Name Here</Name>
                <Longitude>123.45</Longitude>
                <Latitude>123.45</Latitude>
            </Capital>
        </Country>
    </Countries>

Αυτό δεν είναι και τόσο εντυπωσιακό βέβαια. Λίγο πιο εντυπωσιακό είναι ότι μπορεί κανείς να εισάγει expressions a la ASP χρησιμοποιώντας το <%= %> συντακτικό, πχ

Dim countriesWithCapital As XElement = _
    <Countries>
        <Country Name='<%=CountryName%> Density=<%=Density%>> 
            <Capital>
                <Name><%=Name%></Name>
                <Longitude><%=Longitude%></Longitude>
                <Latitude><%=Latitude%></Latitude>
            </Capital>
        </Country>
    </Countries>

Αυτό από μόνο του και πάλι δεν είναι τρομερά εντυπωσιακό, ωστόσο αν συνδυαστεί με ένα LINQ query τότε, έχουμε κάτι πολύ εντυπωσιακό:

Dim countriesWithCapital As XElement = _
    <Countries>
    <%= From country In Countries, city In Capitals _
        Where country.Name = city.Country _
        Select <Country Name=<%= country.Name %>
                        Density=<%= country.Population / country.Area %>>
                  <Capital>
                   <Name><%= city.Name %></Name>
                   <Longitude><%= city.Longitude %></Longitude>
                   <Latitude><%= city.Latitude %></Latitude>
                  </Capital>
               </Country> _
    %>
    </Countries>

Έχουμε ξαναπεί ότι το παραπάνω κάνει τη VB.NET dynamic language, όπερ σημαίνει ότι πλέον μπορούμε να κάνουμε ένα σωρό πολύ εντυπωσιακά κόλπα όπως φαίνεται σε αυτό το απίθανο post της Beth Massi: http://blogs.msdn.com/bethmassi/archive/2007/10/23/avoid-underscores-in-your-multiline-strings.aspx

Θεωρώ ότι τα XML Literals δίνουν ένα πολύ δυνατό code deneration εργαλείο. ΟΚ, υπάρχουν λύσεις όπως το CodeSmith ωστόσο μόνο το γεγονός ότι σε όλα τα παραπάνω έχουμε Intellisence αφήνει τα υπόλοιπα πολύ πίσω. Επιπρόσθετα, εφόσον πρόκειται για κώδικα .NET τότε μπορούμε και μιλάμε για τη δυνατότητα inheritance μέσα στο code pattern, πράγμα πολύ σημαντικό για περίπλοκα code gen σενάρια.


Τα Relaxed Delegates, εύκολα κι απλά…


Τα Relaxed Delegates είναι ένα νέο feature της VB 9 και αν αρέσουν τόσο πολύ στον Don Box, τότε μάλλον αξίζει να τα δούμε λίγο παραπάνω.

Ας αρχίσουμε από ένα VB 8 σενάριο ώστε να φανεί καλύτερα που χρησιμεύουν. Στο .NET Framework 2.0 εμφανίστηκαν τα generics που μας έδωσαν τη δυνατότητα να κάνουμε κάτι σαν το παρακάτω:

    Dim count As Integer
    Dim process As New Action(Of Customer)(AddressOf ProcessElement)

    Sub ProcessElement(ByVal element As Customer)
        count = count + 1
        Console.WriteLine("{0}: {1}", count, element)
    End Sub

    Sub Main()
        Dim list() As Customer = Customer.CreateCustomers
        count = 0
        Array.ForEach(Of Customer)(list, process)
 
        Console.ReadLine()
    End Sub

Το Array.ForEach method παίρνει ως όρισμα ένα array (duh!) και ένα delegate τύπου Action(Of T). Αυτό που κάνει είναι να περνάει ένα προς ένα κάθε item του Array στο delegate (ουσιαστικά στη μέθοδο ProcessElement), δηλαδή χρησιμοποιούμε αυτό αντί να γράφουμε loops.

Αυτό που πρέπει να παρατηρήσετε είναι ότι παντού (στο delegate function, στο delegate declaration αλλά και στο ForEach) χρειάζεται να ορίσουμε τον τύπο των περιεχομένων του Array. Για να φανεί αυτό έγραψα την εκτεταμένη μορφή του κώδικα. Η πιο compact (αλλά ισοδύναμη) είναι η παρακάτω:

    Dim count As Integer
 
    Sub ProcessElement(ByVal element As Customer)
        count = count + 1
        Console.WriteLine("{0}: {1}", count, element)
    End Sub

    Sub Main()
        Dim list() As Customer = Customer.CreateCustomers
        count = 0
        Array.ForEach(list, AddressOf ProcessElement)
Console.ReadLine() End Sub

Είναι προφανές από τα παραπάνω ότι σε περίπτωση που πέρα από objects τύπου Customer είχαμε και objects τύπου Order, τότε θα έπρεπε να φτιάξουμε την αντίστοιχη ProcessElement που να έχει όρισμα ένα Order object. Τα relaxed delegates της VB 9 come to the rescue! Στη VB 9 είναι απόλυτα σωστό το παρακάτω:

    Dim count As Integer

    Sub ProcessElement(ByVal element As Object)
        count = count + 1
        Console.WriteLine("{0} - {1}", count, element)
    End Sub

    Sub Main()
        Dim list() As Customer = Customer.CreateCustomers
        count = 0
        Array.ForEach(list, AddressOf ProcessElement)
        Console.WriteLine("{0} elements were processed.", count)

        Console.ReadLine()
    End Sub

Το list μπορεί να περιέχει οτιδήποτε μέσα, θα εξυπηρετηθεί όμως όπως και να έχει από την ProcessElement. Για την ακρίβεια η ProcessElement μπορεί να μην παίρνει ούτε καν παράμετρο! Ουσιαστικά η VB κάνει πλέον implicit conversion και στα delegates [:)]

Μια πιο απλή και πρακτική εφαρμογή των παραπάνω είναι το εξής σενάριο: Μέχρι και τη VB 8, αν ήθελα αντιστοιχήσω τον ίδιο κώδικα σε δύο διαφορετικά events (πχ KeyDown και MouseDow) θα έπρεπε να έφτιαχνα δύο handlers καθώς η KeyDown έχει ως δεύτερο όρισμα το e τύπου KeyEventArgs ενώ στη MouseDown το e είναι τύπου MouseEventArgs. Τώρα μπορώ απλά να πω:

    Private Sub JustDown() Handles Button1.KeyDown, Button1.MouseDown

    End Sub

Cheers!


Object initializers, Type Inference & Anonymous Types


Ένα χαρακτηριστικό της VB 9 είναι ο νέος τρόπος που μπορεί κανείς να κάνει initialize objects. Παλιότερα, για να φτιάξουμε ένα object θα έπρεπε να πούμε κάτι σαν το παρακάτω:

Dim emp As New Employee()
With emp
    .Name = "John Smith"
    .DepartmentID = 123
    .Salary = 1500
End With

Τώρα πλέον μπορούμε (απλούστερα) να πούμε:

Dim emp2 As Employee = New Employee With {.Name = "Ann", .DepartmentID = 123, .Salary = 1500} 

Σε περίπτωση που δεν συμπεριλάβουμε όλα τα properties στα οποία θέλουμε να δώσουμε τιμές, τότε όσα παραλείψουμε θα αποκτήσουν τη default τιμή. Μπορούμε ακόμα να πούμε και:

Dim emp2 As Employee = New Employee With {} 

Για να μπορούμε να χρησιμοποιήσουμε το παραπάνω συντακτικό, θα πρέπει η κλάση να έχει default constructor. Μάλιστα, πάντοτε χρησιμοποιείται αυτός, ακόμα κι αν υπάρχει parameterized constructor που «ταιριάζει» περισσότερο με τον object initializer που χρησιμοποιούμε.

Πακέτο με τον νέο initalizer πηγαίνει κι άλλο ένα χαρακτηριστικό που ονομάζεται Type Inference. Μπορούμε να παραλείψουμε το As clause και ο compiler θα καταλάβει τον τύπο της μεταβλητής βάσει του ορίσματος.

Dim intSomething = 1
Dim strSomething = "John Smith" 

Τα παραπάνω μπορεί να φέρνουν στο νου άσχημες αναμνήσεις από VB 6 και late binding ωστόσο τώρα έχουμε strong types και όχι generic objects. Αυτό θα το διαπιστώσετε καθώς για το intSomething και strSomething θα έχετε κανονικό IntelliSense! Παράλληλα, το Option Infer Off μπορεί να χρησιμοποιηθεί για να ακυρώσουμε αυτήν την συμπεριφορά.

Έτσι, το προηγούμενο παράδειγμα μπορεί να ξαναγραφτεί ως εξής:

Dim emp3 = New Employee With {.Name = "Ann", .DepartmentID = 123, .Salary = 1500} 

Ενώ μπορούμε να φτιάξουμε εύκολα ένα array:

Dim emps() = { _
          New Employee With {.Name = "Ann", .DepartmentID = 123, .Salary = 1500}, _ 
          New Employee With {.Name = "John", .DepartmentID = 125, .Salary = 1300}, _
          New Employee With {.Name = "Paul", .DepartmentID = 128, .Salary = 1600}}

Τέλος, υπάρχουν και τα anonymous types:

Dim emp4 = New With {.Name = "Rick", .DepartmentID = 130, .Salary = 1280} 

Όταν ο compiler συναντήσει κάτι τέτοιο, τότε δημιουργεί ένα νέο object με τύπο που καθορίζεται on the fly και έχει ως properties αυτά που έχουμε καθορίσει στον initializer, δεν χρειάζεται δηλαδή να καθορίσουμε class definition.

Δοκιμάστε το παρακάτω πείραμα:

        Dim emp1 = New With {.Name = "Ann", .DepartmentID = 123, .Salary = 1500}
        Dim emp2 = New With {.Name = "John", .DepartmentID = 125, .Salary = 1300}
        Dim emp3 = New With {.Name = "Paul", .DepartmentID = 128, .Salary = 1600, .HireDate = #1/1/2001#}
        Dim emp4 = New With {.Name = "Paul", .DepartmentID = 128,  .HireDate = #1/1/2001#, .Salary = 1600}
        Console.WriteLine(emp1.GetType)
        Console.WriteLine(emp2.GetType)
        Console.WriteLine(emp3.GetType)
        Console.WriteLine(emp4.Equals(emp3))

Tα Anonymous Types χρησιμοποιούται περισσότερο ως temporary objects. Δεν έχουν methods και δεν μπορεί να χρησιμοποιηθεί το type definion που έχουν σε delegates κλπ.