LINQ on Mobile Devices – Some Big Surprises from Visual Basic

You Can Take it With You

Syndication

News

  • Don't miss the next Windows Mobile Webcast... Unit Testing for Mobile Devices: http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032382824&EventCategory=4&culture=en-US&CountryCode=US.

As you probably know… yesterday I posted the samples and some screen shots from my LINQ on Mobile Devices webcast. A short time later I received feedback asking that I post VB versions of the samples.

The feedback author made a very good point that these examples (especially the last one) are very difficult to translate from C# to VB if you're not already familiar with C#. Certainly a reasonable request so I figured a take an hour or two today and convert them to VB…

Well so much for an "hour or two" … I started working on them at 10:30 AM and didn't finish until after 5:00 PM … Let's just say that converting the last demo to VB was a little more than just difficult. J

BTW: This isn't going to be a VB bash-fest where I keep touting the amazing power of the C# compiler. I will draw parallels between the two compilers just to provide a reference to an alternate implementation. In the interest of full disclosure, I will say that I do find the C# compiler's handling a of LINQ queries easier to follow than that of the VB compiler.

Sample Downloads

Before I go into the explanation, let me give you the links to the VB Samples first (remember the first demo was already VB)

  • Second demo – Shows the time and resource cost of using a DataSet as compared to a SqlCeResultSet
  • Third demo – Shows the benefit of using a custom extension methods and enumerators to query the SSC database tables directly (Be sure to read the below explanation)

For C# versions of the demos, see yesterday's post.

Some Unexpected Behavior in VB's Processing of LINQ Queries

Some of you will remember earlier this month when I ran into what appeared to be a bug when performing LINQ queries against DataTables in VB – as we later learned, it was due to subtle differences between the C# and VB compilers combined with a subtle difference between Extension Method support in .NET CF and the full framework. With that in mind, I'm going to be a little more cautious with the issue I encountered today. J

I'm not prepared to call what I've run into a bug but I can honestly say that I encountered what I found to be very unexpected behavior when attempting to use custom Extension Methods in VB LINQ queries. Let me explain…

As you'll recall from the webcast, we defined a custom Where extension method for SqlCeResultSet. This method allows us to provide a "smart" enumerator which we can use to expedite our queries. In VB, that extension method is declared as follows.

<Extension()> _
Public Function Where(ByVal resultSet As SqlCeResultSet, ByVal theFunc As Func(Of SqlCeUpdatableRecord, Boolean)) As IEnumerable(Of SqlCeUpdatableRecord)
Debug.WriteLine("Where(resultSet As SqlCeResultSet")
Return New PrepAwareEnumerableWrapper(resultSet, theFunc)
End Function

When we did this in C#, our custom Where extension method immediately worked – there were really no significant caveats. I didn't have the same experience in VB J. Here's what I initially found interesting when doing this in VB … using the method syntax to query the SqlCeResultSet works as expected but the query syntax won't even compile!

resultSet.Where(Function(order) order("Ship Country") = "UK") ' Works

Dim records = From order In resultSet _
Where order("Ship Country") = "UK" ' Won't compile?!?!

The problem as reported by the compiler is that the query syntax is trying to use late binding – the compiler believes that "order" is of type Object rather than inferring that it's a SqlCeUpdatableRecord as expected.

In order to get the method to compile, I tried declaring the data type of "order" explicitly [ From order as SqlCeUpdatableRecord in resultSet ] – I also tried letting the compiler infer the type and then using a cast in the Where clause [Where DirectCast(order, SqlCeUpdatableRecord)("Ship Country") = "UK" ]. But when I run the code, the program uses one of the built-in Where extension methods rather than our custom Where extension method.

Ugh! – not what I was hoping for.

Getting a Handle on the VB Compiler's Type Inference within LINQ Query Syntax

Obviously these difficulties with the query syntax were a surprise. As I understand LINQ handling, the query syntax should be transformed into the method syntax – in fact, I know that's how it works in C#. After several hours of research, I now understand that the direct query-syntax-to-method-syntax translation is sort of what happens in VB … but not exactly. I think the best way to explain how things work is with some examples.

Let's assume that I have a class named BizObjectManager that represents a business object that manages lists of other subordinate business objects. The subordinate business object class is called ChildBizObject whose definition looks like the following.

Public Class ChildBizObject
Public ReadOnly Property SalesDivision() As String
'...
End Property
'...
'...
End Class

We'll query the BizObjectManager object for a list of ChildBizObject objects using both the method and query syntax.

Dim manager As New BizObjectManager

Dim records1 = manager.Where(Function(child) child.SalesDivision = "SouthEast") ' method syntax

Dim records2 = From child In manager _
Where child.SalesDivision = "SouthEast" ' query syntax

And we have the following Where extension method that we hope will be used when doing the LINQ queries.

<Extension()>_
Public Function Where(ByVal manager As BizObjectManager, ByVal theFunc As Func(Of ChildBizObject, Boolean)) As IEnumerable(Of ChildBizObject)
'...
End Function

Now let's look at 3 possible ways that we might declare the BizObjectManager class. The method syntax works with all 3 of the declarations but I think you'll be surprised (I certainly was) at the way the VB compiler's handling of the query syntax changes based on that declaration.

Example 1 – Class implements no interfaces

Public Class BizObjectManager
'...
End Class

In this case the VB compiler reports that the class BizObjectManager cannot be queried. This isn't quite true. As I mentioned a few lines back, the method syntax compiles and works fine. Also, this same situation compiles and works fine in C#. The bottom-line is that there is a Where extension method returning an IEnumerable(of T) available so the class is query compatible.

It appears that the VB compiler will only allow classes that implement specific interfaces to appear in the query syntax. Those interfaces include IEnumerable, IEnumerable(of T), and possibly others.

Example 2 – Class implements the strongly-typed IEnumerable(of T) interface

Public Class BizObjectManager
Implements IEnumerable(Of ChildBizObject)
'...
End Class

So to satisfy the compiler, we have our class implement IEnumerable (of ChildBizObject). This code now compiles fine. But what will happen when we run the code?

Since the VB compiler is requiring us to implement this interface, which Where extension method will it choose: the built-in Where extension method that accepts an IEnumerable(of T) or our Where extension method that accepts the BizObjectManager?

The standard rules for method overloading dictate that the method that most closely matches a type be used; in this case the closest match is our custom Where extension method. The good news is, the compiler does choose the correct Where extension method … ours (closest type match). What that means though, is that to get past the VB compiler I had to implement IEnumerable(of T) even though the compiler won't actually use it. The IEnumerable(of T) method implementations can literally return the value "Nothing".

Example 3 – Class implements the loosely-typed IEnumerable interface

Public Class BizObjectManager
Implements IEnumerable
'...
End Class

This is exactly the case with SqlCeResultSet – it implements only IEnumerable not IEnumerable(of T). So just like SqlCeResultSet, the BizObjectManager query won't compile unless we explicitly declare the type of "child" in our query or cast "child" in the Where clause – but once it compiles, when it runs it uses a built-in Where extension method instead of our custom Where extension method.

What's happening in this case is that the VB compiler appears to want to bias the type towards being some version of IEnumerable(of T). Let's assume that in order to get the code to compile that I've modified the query so that there's a cast in the Where clause – like the following…

Dim records = From child In manager _
Where DirectCast(child, ChildBizObject).SalesDivision = "SouthEast"

The compiler then generates code similar to the following (I've simplified the code for clarity).

Dim records = Manager.Cast(Of Object)().Where(DirectCast(child, ChildBizObject).SalesDivision = "SouthEast")

Basically it appears that once the VB compiler sees that the class implements the loosely-typed IEnumerable [members are returned as object references] the compiler then calls the Cast method which generates a new collection containing the members of the original collection but this time the collection is typed as IEnumerable(of Object) [members are returned as … object references J]. I'm not sure why the compiler does this [I'd guess it's related to the same issue that causes the compiler to require that objects being queried implement IEnumerable/IEnumerable(of T)] but the net-effect is that the type being queried is now IEnumerable(of T) and therefore the built-in Where extension method gets used.

OK … So How'd You Get the SqlCeResultSet-based Extension Method to Work in VB

I don't mean to leave you hanging but this post is already so much longer than I wanted it to be – I'm going to call it a night here and post the details of how I worked around the issue tomorrow.

If you just can't wait, you can download the VB version of Demo three and checkout the code directly.

Oh … I wanted to mention that the webcast that goes with all of this is now available On-Demand.

See ya tomorrow.


Posted Feb 28 2008, 08:57 PM by jim-wilson

Comments

Freesc wrote re: LINQ on Mobile Devices – Some Big Surprises from Visual Basic
on 02-28-2008 10:46 PM
hi,Jim

this is cool ,
and any CSharp samples?

Regards

Freesc
Jim Wilson wrote re: LINQ on Mobile Devices – Some Big Surprises from Visual Basic
on 02-29-2008 6:04 AM
Freesc;

Yep - you'll find the C# examples back in my Wednesday post - they're located near the bottom of the page...
http://www.pluralsight.com/blogs/jimw/archive/2008/02/27/50325.aspx

-Jim
Freesc wrote re: LINQ on Mobile Devices – Some Big Surprises from Visual Basic
on 03-03-2008 10:13 AM
Thanks a lot~

That helps
Jim Wilson wrote re: LINQ on Mobile Devices – Some Big Surprises from Visual Basic
on 03-03-2008 10:17 AM
Freesc;

I just posted the followup to this post a short time ago. http://www.pluralsight.com/blogs/jimw/archive/2008/03/03/50362.aspx

The new post explains exactly what had to be done to the get the final example to work as expected within VB.

-Jim
Alessandro Del Sole wrote re: LINQ on Mobile Devices – Some Big Surprises from Visual Basic
on 05-10-2008 1:56 AM
This is really interesting. But I have a couple of questions :-)

1. So, LINQ-to-SQL isn't available on .NET CF, is this right?
2. Do you please know where can I find a complete list of .NET CF limitations for LINQ against .NET Framework?

Thanks in advance,
Alessandro
Jim Wilson wrote re: LINQ on Mobile Devices – Some Big Surprises from Visual Basic
on 05-12-2008 6:53 AM
Alessandro;

LINQ does not support LINQ-to-SQL ... well, technically, it doesn't support LINQ-to-SQL-Server-Compact.

The issue is that providing LINQ to a syntax-based query engine (SQL Server, SQL Server Compact, Oracle, etc.) requires LINQ-to-Entities which is a very complex, storage intensive, and memory intensive library that isn't very well suited for the limited resource environment of today's mobile devices.

Basically, .NET CF 3.5 provides the following LINQ support
- Standard Query Operators (also know as LINQ-to-Objects)
- LINQ-to-DataSet (includes DataTable)
- LINQ-to-XML (except XPath-based extension methods like XPathEvaluate, etc.)

The only resource I know that lists what's supported in .NET CF 3.5 is "LINQ in the .NET Compact Framework" ( http://msdn.microsoft.com/en-us/library/bb397834.aspx ) - the info there is pretty limited. In fact I'm not sure if it lists as much detail as I've just mentioned above. :-)

Basically you have the 3 supported implementations (Objects, DataSet, XML) and for the most part the implementations are pretty complete but you may find functions here-and-there that aren't supported.

You might want to checkout some of my posts on issues I've run into ... Just checkout my lists of posts from February, March, and April 2008 (you've probably already seen some of them - I'd post the individual links here in my reply but then my blog server thinks my reply is spam :-S )

I hope that helps,
Jim
Beth Massi wrote re: LINQ on Mobile Devices – Some Big Surprises from Visual Basic
on 05-29-2008 9:42 AM
Hi Jim,

I spoke to the VB team on this one. The issue technically has to do with VB's query rules, not with extension methods.

In order to support queries, a “Select” method must be defined, even if it is not used.
VB uses the definition of the “Select” method to infer what the element type of the collection is (the compiler does this by looking at the type of the parameter on the delegate argument).

If no select method is defined, it then tries to bind to an AsQueryableMethod or an AsEnumerableMethod and sees if the return types of those methods define a valid select method.

If it fails to bind to either of those, the compiler will finally try to bind to “Cast(of Object)”, and see if it’s return value has a valid select method defined on it. If so, it then uses that method.

In this case, because only a where method was defined, the SqlCeResultSet is not directly queryable. As a result, the compiler ends up binding to the Cast(of Object) extension method, which yields an IEnumerable(of Object). This causes everything to bind to the IEnumerable query methods, and for the compiler to use Object as the element type.

This works in C# because C# just does a pure syntax translation, and so it binds directly to the Where extension method without bothering to look for a select extension method.

This does make defining query operators in C# easier than in VB. The tradeoff is that VB has better query intellisence.

In any case, you can fix this by adding a Select extension method to SqlCeResultSet.

Hope that helps shed some light on the differences. It's a pretty easy fix.

-Beth

Add a Comment

(required)  
(optional)
(required)  
Remember Me?