title image

journal.nullschool.net


Archive for July, 2005

Late-bound conversions with ChangeType

Friday, July 22nd, 2005

I was reading Paul Vick’s entries (1) (2) on dynamism, and it reminded me of a problem I encountered a short time ago.

Lately, I’ve been working with datasets and validation of user-supplied data. This data, entered as strings into bound controls, requires validation to make sure it converts to the underlying field types. Perhaps it’s my novice-level experience, but doing validation like this is a real pain.

The problem is that at some point the inputted string has to be converted to the underlying type U. To be robust, this conversion code requires as many unique code paths as there are possibilities for U. Looking only at the types allowed by the XSD designer, that’s 17 possibilities: Boolean, SByte, Byte, Short, UShort, Integer, UInteger, Long, ULong, Decimal, Single, Double, Date, Char, String, TimeSpan, and Guid. The corresponding conversion code paths need to be somewhere, whether written by hand or contained in a self-validating control. But consider that U could be one of the Sql types, such as SqlInt32, or one of the new Nullable types, such as Nullable(Of Integer), or even a user-defined type. The possibilities for U are theoretically limitless, and it’s unlikely that specialized, hand-written code or self-validating controls purchased from a vendor can handle data validation in these situations. Furthermore, I have yet to find self-validating controls that offer robust error handling capabilities, like the ability to display localized error messages or to throw richly-typed exceptions with detailed context information. In the end you’re left with the only option being a hand-written, and expensive, validation mechanism. So what to do?

There’s another place where these conversion code paths are written, and that’s in the Visual Basic runtime. Why not use them? They are contained in a “hidden” VB runtime function that can perform latebound conversions: Microsoft.VisualBasic.CompilerServices.Conversions.ChangeType. We will create generalized code using this function to do all type validation at runtime. In other words, we will defer all type validation to the latebinder. Consider the following code:

Module Example1
Sub Main()

'First create a dataset to work with.

Dim Patients As New DataSet("Patients")

Dim ContactInfo As DataTable = Patients.Tables.Add("ContactInfo")

ContactInfo.Columns.Add("Age", GetType(Integer))

ContactInfo.Columns.Add("Weight", GetType(SqlTypes.SqlDouble))

ContactInfo.Columns.Add("LastVisit", GetType(Nullable(Of Date)))

'Next, call a generalized function to add a row to the dataset.

AddRow(ContactInfo, "29", "63.4", "6/2/2005")

'Lastly, print out the results.

For Each Value As Object In ContactInfo.Rows(0).ItemArray

Debug.WriteLine(Value.ToString & " : " & Value.GetType.ToString)

Next

End Sub

Function AddRow( _

ByVal Table As DataTable, _

ByVal ParamArray Values As Object()) As DataRow

'This function adds any values to any dataset by performing

'the neccesary conversions to the underlying field types at runtime.

Dim Index As Integer = 0

Dim Count As Integer = Math.Min(Table.Columns.Count, Values.Length)

While Index < Count

'Convert each value to the underlying field type.

Values(Index) = _

CompilerServices.Conversions.ChangeType( _

Values(Index), _

Table.Columns(Index).DataType)

Index += 1

End While

'Add the row of converted values.

Return Table.Rows.Add(Values)

End Function

End Module

Compiling and running outputs:

29 : System.Int32

63.4 : System.Data.SqlTypes.SqlDouble

6/2/2005 12:00:00 AM : System.Nullable`1[System.DateTime]

This code creates a dataset with fields of type Integer, SqlDouble, and Nullable(Of Date). The user supplies the text “29″, “63.4″ and “6/2/2005″, and these strings are converted into the underlying types and added into the dataset. One function, AddRow, performs most of the work. It loops through each value, converting it to the appropriate column type. If the conversion fails an exception occurs. If it succeeds for all values, a row is inserted into the table with the correctly converted values. Notice that we can’t use a normal CType operator because it requires the type of the column to be known at compile-time. Also notice that column types such as SqlDouble and Nullable(Of Date) work correctly. These types rely on operator overloading for conversions, which means that Conversions.ChangeType is performing operator overload resolution at runtime and invoking the appropriate methods.

Actually, the .Net framework has a function which gets us partially towards a solution: System.Convert.ChangeType(value As Object, conversionType As System.Type). The documentation is quite vague, but this function also makes latebound conversions. Unfortunately, System.Convert.ChangeType supports only core conversions of the .Net world, such as along lines of inheritance and between intrinsics. It does not perform operator overload resolution and therefore does not offer a complete solution. For example, it could not handle SqlDouble or Nullable(Of Date) fields.

Why Visual Basic’s ChangeType is “hidden” in the CompilerServices namespace rather than exposed as a full fledged member of the language is an uninteresting story, but basically I lost that argument and it remains hidden and “unsupported”. Thankfully, it’s Public because the compiler needs to generate calls to it in certain scenarios (such as the copy-out conversion of an argument to a late-bound call), meaning it’s callable from user-code.

I’ve been reading in the documentation that handling the Format and Parse events of a Binding object allows custom data conversions between datasets and bound controls (see here if you have VS 2005 Beta2 installed). By using ChangeType, we need only one 1-line function to handle both of these events for all controls in the program. Add these lines to the end of Sub Main (assumes a form called InfoForm with a WeightTextBox control and a button control):

'Create a binding between the dataset and the TextBox control.

Dim InfoEntry As New InfoForm

Dim Binding As Windows.Forms.Binding = _

New Windows.Forms.Binding("Text", Patients, "ContactInfo.Weight")

'Hook-up the binding events with a generalized converter function.

AddHandler Binding.Format, AddressOf Convert

AddHandler Binding.Parse, AddressOf Convert

InfoEntry.WeightTextBox.DataBindings.Add(Binding)

'Display the form.

System.Windows.Forms.Application.Run(InfoEntry)

Now add this sub:

Sub Convert( _

ByVal Sender As Object, _

ByVal Cevent As Windows.Forms.ConvertEventArgs)

Debug.WriteLine("convert to " & Cevent.DesiredType.ToString)

'Use a latebound conversion to convert the incoming value into

'any desired type.

Cevent.Value = _

CompilerServices.Conversions.ChangeType( _

Cevent.Value, _

Cevent.DesiredType)

End Sub

That’s it. By using the example function above, any control can be bound to any data column (assuming the type conversion succeeds of course!). Run the code and enter some values in the textbox. You should get output similar to:

convert to System.String

convert to System.String

convert to System.Data.SqlTypes.SqlDouble

convert to System.String

convert to System.Data.SqlTypes.SqlDouble

convert to System.String

I only wish I was using Visual Studio 2005 at work. Code like this would make my life so much easier. Three words of caution:

  1. ChangeType only does type validation, not value validation such as checking for negative weight or other invalid values. You still have to write or purchase this code.
  2. ChangeType costs execution time because it is latebound. But keep in mind that we’re validating user-entered code here. The cost of calling ChangeType is miniscule compared to the time it takes the user to lift their finger from the Enter key.
  3. ChangeType is supposed to be “hidden”. It is not a first-class member of the Visual Basic language.

In my next post I will take the usage of ChangeType further and show how it can be applied to Generic type parameters to provide even more generalization.

Ordered 100 Mbps connection

Thursday, July 21st, 2005

I saw this article on Slashdot today talking about 100 Mbps becoming available in Finland next year. This kind of service is already available in Tokyo through NTT and OCN called “OCN 光 with フレッツ” (OCN Hikari with Flets). If I understand it all correctly, this is fiber service to the home through NTT with OCN (a separate company) as the ISP. I ordered this for my new apartment last week. The fiber itself is 1 Gbps but gets converted to 100 Mbps at the router(?) when it enters the apartment building. Unfortunately, my building does not have fiber installed yet, so the installation is going to take about 1.5 months. As for price, the first two months (starting from when?) are free and the next five are ¥3,570/mo., afterwards becoming the standard rate of ¥6,720/mo. Disconnection within two years of service start costs ¥5,250, and becomes free after that. So far the application process has not gone smoothly because the hardware consultant handed me off to the English-speaking software support department, who of course have a) nothing to do with fiber installation and b) have no idea who I am or where my application is. This is going to be really fun, I can tell.

Darth Vader hanging out with schoolgirls

Thursday, July 14th, 2005

Darth Vader and schoolgirls

Translation: “Star Wars has arrived on au[*]!”
Spotted in: JR Shibuya Station

*au is a cellular service provider in Japan