July 2007 - Posts

If you've installed the Beta 2 samples for VB Orcas 2008, then you will likely find the solutions don't open.  Problem is they are marked as being for "Orcas" not "2008".  This quick and dirty code will fix the problem:

 

With My.Computer.FileSystem

   Dim samplesPath = .SpecialDirectories.MyDocuments & "\VB Samples Beta2"

   For Each file In .GetFiles(samplesPath, FileIO.SearchOption.SearchAllSubDirectories, "*.sln")
      Dim contents = .ReadAllText(file)
      contents = contents.Replace("# Visual Studio Codename Orcas", "# Visual Studio 2008")
      .WriteAllText(file, contents, False)
   Next

End With

with 5 comment(s)
Filed under: , , ,

OMG, I couldn't believe my eyes....

 

I went to open a New Project, and although the dialogue looks the same as previous ones, I thought I'd try to re-size it.  Much to my amazement it did !!!!!

 

Yes, 4 versions later, finally you can resize the New Project dialogue windows !!!!!!!!!!!!!!!!!!!!

with 2 comment(s)
Filed under: , ,

Yesterday Beth posted an interview with Erik Meijer showing integrated support for XML in VB.  There's a cool examples of using VB9 with XML documents both working with existing documents and creating new ones.  Some important things to note in the video:

- You use Imports to create namespace aliases at file level

- Add an XSD to your project and intellisense will use that when you work with the XML

- Support for MXL in VB is not just literals as smoke and mirror string parsing, it includes rich intellisense, XML as a first class citizen, and optimal compilation.

The VB teams not finished with XML by any means, but what's in VB9 is pretty awesome.  Unless you are one who believes XML is insignificant, you really must look at VB as the right tool for the job.  The inclusion of xsd driven intellisense really adds some serious icing :)

It was almost two years ago when I first saw the XML in VB begin.  I remember at the time I was sitting in a mini-bus in Redmond one morning about to head out to the campus and we were discussing LINQ and XLINQ.  I said a the time this has the serious potential to replace any need to use XSLT. Two years later and that prediction is looking to be very true and then some. The support for XML in VB combined with LINQ gives delayed evaluation, richness of the .NET framework, an incredibly powerful way to work with and create xml.

with no comments
Filed under: , ,

Beth and I have been having a argument all in fist fight discussion about dynamic code in VB.  My point to Beth in the discussion was that all the code she showed could in fact be made work reasonably easily with Strict On, such is the power of the CallByName function.  In VB 10 we hopefully will also have dynamic identifiers which were originally touted for VB9.  They'll dramatically reduce the need for CallByName.  From memory I think the syntax suggested was info.("Item")(prop) for the same as the CallByName(info, "Item", CallType.Get, prop)

 

Anyway, here's the code I tried pasting into a comment by the formatting went weird.

 

Shared Function GetQuestion(ByVal info As Object, ByVal properties As List(Of String)) As Object
    Dim c As Object
    Dim propValue As Object
    Try

        Dim assemblyName As String = CStr(CallByName(info, "Item", CallType.Get, "Assembly"))
        Dim controlName As String = CStr(CallByName(info, "Item", CallType.Get, "Control"))
        c = System.Reflection.Assembly.Load(assemblyName).CreateInstance(controlName)

        'VB does an automatic conversion at *runtime* when
        ' working with these properties because we don't
        ' know the info object type nor the property types.
        For Each prop As String In properties
            Try
                propValue = CallByName(info, "Item", CallType.Get, prop)
            Catch ex As Exception
                propValue = Nothing
            End Try

            If propValue IsNot Nothing AndAlso propValue IsNot System.DBNull.Value Then
                Try
                    CallByName(c, prop, CallType.Set, propValue)
                Catch ex As Exception
                    'if we can't set a property on the object, just ignore
                End Try
            End If
        Next

    Catch ex As Exception
        'Try/Catch is required here, as this code will cause
        ' a runtime error if the the type cannot be created.
        Dim tbx As New TextBox
        With tbx
            .Text = ex.ToString
            .Multiline = True
            .Height = 100
            .ReadOnly = True
            .ScrollBars = ScrollBars.Vertical
        End With
        c = tbx
    End Try

    Return c
End Function

with no comments
Filed under: , , ,

The VB team has been publishing some LINQ recipes lately.  If you scour their comments you'll probably notice a few suggestions/corrections from me.  The latest sample I think is worth further discussion so I've decided to blog it here.


The sample code they show is a function to select a "page" of data:

 

Public Function GetProductsPage(ByVal db As NorthwindDataContext, _

                                    ByVal pageNum As Integer, _

                                    ByVal pageSize As Integer)

 

        Return From p In db.Products _

               Select p.ProductID, p.ProductName, p.UnitPrice _

               Skip (pageNum - 1) * pageSize _

               Take pageSize

 

 End Function

The first thing you should notice is this function has no return type declared.  That's right it requires Strict Off.  I advise you do NOT do this.

If you decide you really do want to factor this query into a separate function, then for fidelity you should consider creating a type to hold the ProductID, ProductName and UnitPrice fields.   Without this real type, the anonymous type means you can't pass the type information out of the function, so your only choices for the function return type are As Object, As IEnumerable or As IQueryable.  So any code that tried to work with the actual fields would have to do so late bound.  Admittedly in the VB teams example this is not an issue because the data binding is late bound via reflection, but be aware this is a major limitation of anonymous types... if used outside of a function it will require late binding.

 

As to the query itself, it's quite interesting to see the generated SQL.  TO do this, you just need to define a stream for the Data Context's log to be written to, e.g :

Dim db As New NorthwindDataContext
db.Log = Console.Out

That will automatically print to the console the SQL when it is executed.   Alternatively you can use a stream writer and then write the stream out later:

Dim db As New NorthWindDataContext
Dim stream As New IO.StringWriter()
db.Log = stream

and later print that stream out, such as Debug.Print(stream.ToString())

 

And this is the SQL generated from a Skip 20 Take 10

 

SELECT TOP 10 [t1].[ProductID], [t1].[ProductName], [t1].[UnitPrice]
FROM (
    SELECT ROW_NUMBER() OVER (ORDER BY [t0].[ProductID], [t0].[ProductName], [t0].[UnitPrice]) AS [ROW_NUMBER], [t0].[ProductID], [t0].[ProductName], [t0].[UnitPrice]
    FROM [dbo].[Products] AS [t0]
    ) AS [t1]
WHERE [t1].[ROW_NUMBER] > @p0
-- @p0: Input Int32 (Size = 0; Prec = 0; Scale = 0) [20]

 

Somehow the LINQ looks a LOT nicer to me ;)

I was just reading Roger Jennings blog and note he logged a bug in Orcas.  What scared me was the response Roger got.

It seems his bug was acknowledged pretty quickly, and some 5 hours later they said :

Thanks for your feedback. We have reproduced this bug on Visual Studio Codename Orcas June CTP, and we are sending this bug to the appropriate group within the VisualStudio Product Team for triage and resolution.

Seems good right ?  Well not really.  The bug was just a simple syntax mistake.  The "Of " was missing in the generic parameter.  This kind of thing should NOT need triage.

The code was :

Dim Query = From c In dsNwindT.Customers _
                    Join o In dsNwindT.Orders On c.CustomerID Equals o.CustomerID _
                    Join d In dsNwindT.Order_Details On o.OrderID Equals d.OrderID _
                    Join p In dsNwindT.Products On d.ProductID Equals p.ProductID _
                    Where p.ProductID = 2 AndAlso c.Country = "USA" _
                    Order By o.OrderID Descending _
                    Select c.CustomerID, c.CompanyName, o.OrderID, _
                     ShippedDate = o.Field(Nullable(Of DateTime))("ShippedDate"), _
                     p.ProductName, d.Quantity

 

and should have been:

 

Dim Query = From c In dsNwindT.Customers _
                    Join o In dsNwindT.Orders On c.CustomerID Equals o.CustomerID _
                    Join d In dsNwindT.Order_Details On o.OrderID Equals d.OrderID _
                    Join p In dsNwindT.Products On d.ProductID Equals p.ProductID _
                    Where p.ProductID = 2 AndAlso c.Country = "USA" _
                    Order By o.OrderID Descending _
                    Select c.CustomerID, c.CompanyName, o.OrderID, _
                     ShippedDate = o.Field(Of Nullable(Of DateTime))("ShippedDate"), _
                     p.ProductName, d.Quantity

 

or alternatively using the new syntax:

 

Dim Query = From c In dsNwindT.Customers _
                    Join o In dsNwindT.Orders On c.CustomerID Equals o.CustomerID _
                    Join d In dsNwindT.Order_Details On o.OrderID Equals d.OrderID _
                    Join p In dsNwindT.Products On d.ProductID Equals p.ProductID _
                    Where p.ProductID = 2 AndAlso c.Country = "USA" _
                    Order By o.OrderID Descending _
                    Select c.CustomerID, c.CompanyName, o.OrderID, _
                     ShippedDate = o.Field(Of DateTime?)("ShippedDate"), _
                     p.ProductName, d.Quantity

 

For the record, the C# code was :

var query = from c in dsNwindT.Customers
                 join o in dsNwindT.Orders on c.CustomerID equals o.CustomerID
                 join d in dsNwindT.Order_Details on o.OrderID equals d.OrderID
                 join p in dsNwindT.Products on d.ProductID equals p.ProductID
                 where p.ProductID == 2 && c.Country == "USA"
                            orderby o.OrderID descending
                 select new { c.CustomerID, c.CompanyName, o.OrderID,
                            ShippedDate = o.Field<DateTime?>("ShippedDate"),
                            p.ProductName, d.Quantity };

 

 

One thing I will say is the VB team needs to add some greater feedback around the different uses of () in VB.  If they have different meaning they should be colored differently or something.  It should be obvious. And the error correction should suggest the Of

with 3 comment(s)
Filed under: , , ,

http://www.microsoft.com/presspass/press/2007/jul07/07-10WPCDay1PartnersPR.mspx

with no comments
Filed under: , ,

Previously I wrote about my opinion on anonymous types and keys.  This post just tells it how it actually is

(IOW: yes I would still prefer some way to specify the entire type is For ReadOnly rather than having to prefix every property assignment with Key or to use the constructor with named parameters instead.)

 

These samples are per the June CTP:

 

(1)   Dim person = New With {.Name = "Fred", .Age = 42}

Name and Age are read/write, and there is no hash code or equals defined for the class,(only System.Object implementations, hence reference equality only)

 

(2)   Dim person = New With {Key .Name = "Fred", .Age = 42}

Name is ReadOnly, Age is Read/Write. Equals and GetHashCode are defined and are based on the Name property only.

 

(3)  Dim person = New With {Key .Name = "Fred", Key .Age = 42}

Name and Age are both ReadOnly. Equals and GetHashCode are defined and are based on both the Name and Age properties.

 

Of the above, (1) is mutable, (2) is partially mutable, and (3) is immutable.

with no comments
Filed under: , ,

Jim Wooley recently brought my attention to some "peculiar" code in the LINQ to SQL generated properties.  It rang alarm bells with me for a different reason.. that is the runtime behavior for LINQ to SQL classes will be different in VB than it is in C#.

The code in question is in the property sets:

<Column(Storage:="_ContactName", DbType:="NVarChar(30)")> _
Public Property ContactName() As String
    Get
        Return Me._ContactName
    End Get
    Set(ByVal value As String)
        If ((Me._ContactName = Value) _
          = False) Then
            Me.OnContactNameChanging(Value)
            Me.SendPropertyChanging()
            Me._ContactName = Value
            Me.SendPropertyChanged("ContactName")
            Me.OnContactNameChanged()
        End If
    End Set
End Property


The If ((Me._ContactName = Value)  = False) Then  part is a little strange to say the least.  

What they probably wanted was If Me._ContactName <> Value Then
The C# code is if ((this._ContactName != value))

But that is not the problem. VB treats equality of strings slightly different than C#.  In VB if you use = or <>, null references are treated as being equal to String.Empty. That is If Nothing = "" Then is True.

So the question is what difference will this difference make ?  Well if you are using Nothing to represent dbNull when saving the data, (assuming the field is nullable), then the result is the field is set.  Implications of that include a plethora of concurrency issues.
Of course, data binding and standard text box controls have never been good in this regard anyway.  But it would be preferable if both C# and Vb behaved the same.  In this case using String.Equals would address that :

  If String.Equals(Me._ContactName, Value) Then

 

The ADO.NET Entity model on the other hand at present doesn't even do a check of the existing value. Here's the same code for the ContactName property set, this time using ADO.NET Entity.

Set
      Me.ReportPropertyChanging("ContactName")
      Me._ContactName = System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, true, 30)
      Me.ReportPropertyChanged("ContactName")
End Set

So with the Entity model you need to check that yourself and watch for a lot of superfluous change notifications when really no change has occurred.  I presume they'll change that otherwise it would result in a lot of un-necessary writes to the database, as well as be a real pain to use in any event driven designs.

with no comments
Filed under: , ,

Many of the little features are in VB Orcas as of June (no need to wait till Beta 2)

 

(1)  Lambda functions are in.

Dim f = Function(x As Int32) x + 1
Dim y = f(2)
' y now equals 3

 

(2)  nullable type support

This includes declaration of nullable types with ?, and operators elevation from the non nullable type with null propagation.

Dim i? as Int32     ' i is as Nullable(of Int32)
Dim j as Int32?    ' j is also a Nullable(Of Int32)

Dim k = i + j    ' k is inferred to be Nullable(Of Int32)

Note how you can use the + operator on the Nullable(Of Int32)'s.  If either i or j is null, then k will be null.

 

(3) new If operations

Dim name as String = If(customer IsNot Nothing, customer.Name, "")

The If function replaces the IIf function except unlike IIf, If(,,) only evaluations the parts used.  Given the above example, if customer was null, the customer.Name expression would cause a null reference exception when using IIf because it is a function and all parameters need to be evaluated. If(,,) is actually a ternary operator so only the parts needed are evaluated, which in this case avoids the null reference exception.

 

A second If operator is also available: If(,).  This operator only takes two operands and is designed for work with nullable types.

Dim i as Integer?
Dim n As Integer = If(i, 0)

Basically this translates to If i is not null then return i's value, otherwise return the second operand value 0

 

(4) LINQ

 

LINQ seems to be in full force as of Orcas.  I haven't fully tested it yet but so far everything works.  Note some things are different in VB compared to C#, for example see my previous post on Group By in VB. In C# Groupy By returns an IGrouping(key, IEnumerable(data)), whereas VB returns an IEnumerable(key, IEnumerable(data)).

with 1 comment(s)
Filed under: , ,

(note the title is phonetic <g>)

I thought I'd write a simple Group By query to list the controls on a form grouped by their type. 

 

Dim query = From c In Me.Controls Group c By c.GetType.Name Into Members

 

Then to write out the code I wrote:

For Each group In query
    Debug.Print("----- Control Type =" & group.Name & "-------")
    For Each cntl As Control In group.Members
        Debug.Print(cntl.Name)
    Next
Next

That'll list the controls grouped by their type. Pretty simple, but we can improve the query a bit.

 

(1)  First improvement is to make it more strongly typed. The Me.Controls returns a ControlsCollection which was designed before generics, so it's IEnumerable is As Object.  This isn't obvious in the above query , and you avoid the issue of casting when you iterate by specifying For each cntl As Control In ....  Currently you can't specify the cast to be made on the c variable in the From clause, but you can effectively cast the Controls collection via the Cast(Of TResult) extension.  The query hence becomes :

 

Dim query = From c In Me.Controls.Cast(Of Control)() Group c By c.GetType.Name Into Members

 

Now the query is strongly typed through-out.

 

 

(2) the next thing you might want to do is sort the grouping.  There's two levels of sorting you can do, one is inside the group, and the other is sorting the group itself.  To sort the groups, we add a Order By Name to the end of the query.

 

Dim query = From c In Me.Controls.Cast(Of Control)() Group c By c.GetType.Name Into Members Order By Name

 

The "Name" variable is created from the c.GetType.Name part of the Group By clause, so you are really sorting by the calculated value of c.GetType.Name.  You could give the grouping criteria a different name if you wanted, such as TypeName might be appropriate here :

 

Dim query = From c In Me.Controls.Cast(Of Control)() Group c By TypeName = c.GetType.Name Into Members Order By TypeName

 

If you want to also sort the controls inside each group, you can add an Order By clause before the grouping:

 

Dim query = From c In Me.Controls.Cast(Of Control)() Order By c.Name Group c By TypeName = c.GetType.Name Into Members Order By TypeName

 

(3) Now you probably want to make your code a little more readable and break that whopping one liner into a neater block :

 

Dim query = From c In Me.Controls.Cast(Of Control)() _
                    Order By c.Name _
                    Group c By TypeName = c.GetType.Name Into Members _
                    Order By TypeName

 

(4) Finally, you can make the query a little bit more explicit by including the Select clause.  As it stands the query is implicitly selecting the TypeName and Members, hence the query is returning an IEnumerable(Of String, IEnumerable(Of Control)).  That may not be obvious to people looking at the code, so adding the Select clause helps a bit in spelling that out.

Dim query = From c In Me.Controls.Cast(Of Control)() _
                    Order By c.Name _
                    Group c By TypeName = c.GetType.Name Into Members _
                    Order By TypeName  _
                    Select TypeName, Members

 

 Note: the Select clause is optional in this case.  If you find it adds readability add it

with 1 comment(s)
Filed under: , ,

via Neil's MS UK blog, June Orcas CTP is available here:

http://download.microsoft.com/download/f/2/a/f2ac411f-acf9-42a7-a84f-3efc409bcd6b/VSTS_VPCJuneCTP.mht

with 3 comment(s)
Filed under: , ,

One of the new things in Orcas is Lambda functions (or inline functions/BLOCKED EXPRESSION.  Although useful because they allow you to write code as one blob, they have some drawbacks :

- code is not factored for re-use

- debugging at best feels weird with them

- performance can suffer

Previously I hadn't really thought about performance until today.  Reading Kit George's latest entry on the VB team blog, LINQ Cookbook Recipe 5, Kit shows two ways of performing an aggregate Concat.  The extension method approach could be improved by using a string builder, but how do you use a String builder when using the lambda approach ?  If you can't then at the end of the day, the lambda ends up creating multiple strings for each concat, and in .NET that's one of the no-no's for writing string concatenation.  The extension method though you can refactor and tune to use a StringBuilder.

with no comments
Filed under: , ,