tags:

views:

939

answers:

6

Most of our development is done in vb.net (not my choice) and one frequently used code pattern uses an 'On Error GoTo' followed by a 'Resume Next' so that all database fields can be read with a DirectCast() and any DBNull values are just ignored.

The current code would be

On Error GoTo error_code
oObject.Name = DirectCast(oReader.Item("Name"), String)
oObject.Value = DirectCast(oReader.Item("Value"), Integer)
error_code:
Resume Next

C# code to replace this an enable the removal of the On Error code would be

oObject.Name = oReader["Name"] as string ?? string.Empty;
oObject.Value = oReader["Value"] as int? ?? -1;

The problem is that the vb.net eqivelent of this C# code uses a TryCast() which can only be used for reference types (nullable types are value types) whilst the C# as keyword can be used for reference and nullable types.

So in summary does anyone have an example of vb.net code that does the same thing as the C# code in a single line per database field?

-EDIT-

I've decided what I think is the best solution in our case. Helper methods would not be suitable (due to management) and we can't have extension methods as we're only using .NET 2.0 (though with VS 2008 so we get the If())

oObject.Name = If(oReader.IsDBNull(oReader.GetOrdinal("Name")), String.Empty, oReader.GetString(oReader.GetOrdinal("Name")))
oObject.Value = If(oReader.IsDBNull(oReader.GetOrdinal("Value")), 0, oReader.GetInt32(oReader.GetOrdinal("Value")))
+1  A: 

I don't think that VB.NET has any operator that mimics the functioning of the ?? operator of C#. However, you could use the equivalent of C#'s ternary operator - The IIF function in your case:

Admittedly ugly:

oObject.Name = IIf(oReader.Item("Name").Equals(DBNull.Value), DirectCast(oReader.Item("Name"), String), String.Empty)
oObject.Value = IIf(oReader.Item("Value").Equals(DBNull.Value), DirectCast(oReader.Item("Value"), Integer), -1)

Please make sure to read Stephen Weatherford's post in the link I provided above which suggests a generic IIf function that infers type requirements from the provided arguments. This becomes necessary because the IIf function always returns an Object!

A better option would be to create a function that performs this cast conditionally, rather than trying to do it in one line.

Cerebrus
@Stevo3000: You haven't mentioned the version of .NET you're using. If you're using VB 9.0, @Jose's answer provides a much better option.
Cerebrus
I think you have the parameters backwards. Otherwise that is the correct solution for pre VB 9.0
Mike Brown
I would avoid the IIf() function as it introduces boxing for value types.
Stevo3000
IIf always evaluates both parameters so won't this throw an exception?
MarkJ
+4  A: 

In VB 9.0, "IF" is a true coalescing operation equivalent to C#'s "??". Source MSDN:

So you could use:

oObject.Name = IF(oReader.Item("Name").Equals(DBNull.Value),string.Empty,DirectCast(oReader.Item("Name"), String))
Jose Basilio
Very good point! I didn't even know about this. +1
Cerebrus
Good answer, not quite as succinct as the C# code I posted in my question but definatly the closest answer I've seen yet.
Stevo3000
There's an easier variant for the `If` operator, using two arguments.
Konrad Rudolph
What is the difference between `IF` and `IIF`
masfenix
From MSDN: "An If operator that is called with three arguments works like an IIf function except that it uses short-circuit evaluation. An IIf function always evaluates all three of its arguments, whereas an If operator that has three arguments evaluates only two of them"
Jose Basilio
+2  A: 

Use the IsDbNull method to check for null values instead of a costly try-fail-handlefailure approach. Error handling is almost always more expensive than catching a condition before it becomes an error.

(Also, error handling should use exceptions, not VB6-style ON ERROR GOTO HELL...)

With the conditional If function that would look like this as a one-liner:

oObject.Name = If(oReader.IsDbNull(oReader.GetOrdinal("Name")), Nothing, oReader.GetString(oReader.GetOrdinal("Name")))

I would prefer to write some helper functions that could be used to make the code more readable and more efficient:

Function GetStringOrDefault(reader As DbDataReader, key As String) As String
   Dim ordinal = reader.GetOrdinal(key)
   If reader.IsDbNull(ordinal) Then
      Return Nothing
   Else
      Return reader.GetString(ordinal)
   End If
End Function

Usage:

oObject.Name = GetStringOrDefault(oReader, "Name")

You could alternatively write them as extensions to the DbDataReader class to make them even more readable.

Guffa
+1 for the suggestion to implement as extension methods...I implemented TryGetX methods for all of the GetX methods on IDataReader. Your name GetXOrDefault works better if you allow the user to pass in a default value if the field is DBNull
Mike Brown
100% with you on avoiding VB6 error hell, but as the question states it wasn't my decision (I wasn't even at the company when it was decided).
Stevo3000
@Mike: The GetXOrDefault method could have overloads with and without default value, just like the Nullable.GetValueOrDefault method.
Guffa
A: 

According to the docs TryCast only works with reference types. Regardless, the problem might be more with your usage of the IDataReader than trycast.

Before looping over the rows in the IDataReader, get the ordinal values of the column names and use the strongly typed GetX methods to retrieve the field values like so

Dim name As Integer = oReader.GetOrdinal("Name")
Dim value as Integer = oReader.GetOrdinal("Value")

While reader.Read()
  oObject.Name = if(oReader.IsDbNull(name), string.Empty, oReader.GetString(name))
  oObject.Value = if(oReader.IsDbNull(value), -1, oReader.GetInt32(value)
End While

' Call Close when done reading.
oReader.Close()

Using the if operator with 3 parameters like so makes it work like the C# ternary boolean operator eval?trueExpression:falseExpression

This should help you with your issue.

Mike Brown
+7  A: 

Edit

Sorry for sprouting such nonsense. I relied on a posting by Paul Vick (then head of the VB team) rather than the MSDN and don't have Windows installed to test the code.

I'll still leave my posting – heavily modified (refer to the edit history to read the wrong original text) – because I find the points still have some merit.

So, once again, three things to recap:

  1. For reference types, C#'s as is directly modelled by TryCast in VB.

    However, C# adds a little extra for the handling of value types via unboxing (namely the possibilities to unbox value types to their Nullable counterpart via as).

  2. VB 9 provides the If operator to implement two distinct C# operators: null coalescing (??) and conditional (?:), as follows:

    ' Null coalescing: '
    Dim result = If(value_or_null, default_value)
    
    
    ' Conditional operator: '
    Dim result = If(condition, true_value, false_value)
    

    Unlike the previous IIf function these are real short-circuited operators, i.e. only the necessary part will be executed. In particular, the following code will compile and run just fine (it wouldn't, with the IIf function, since we could divide by zero):

    Dim divisor = Integer.Parse(Console.ReadLine())
    Dim result = If(divisor = 0, -1, 1 \ divisor)
    
  3. Don't use VB6 style error handling (On Error GoTo … or On Error Resume [Next]). That's backwards compatibility stuff for easy VB6 conversion. Instead, use .NET's exception handling mechanisms like you would in C#.

Konrad Rudolph
Point 1 is incorrect, as is not directly modelled by TryCast.
Stevo3000
This means that your code does not work (even if oReader.["Name"] is correctly set to oReader("Name")). Your code is what I was hoping to be able to do. But the limitations of vb.net over C# stop it from working.
Stevo3000
Stevo3000: Sorry. I've rewritten the answer. Unfortunately, this doesn't help you at all but I found some points worth making nontheless.
Konrad Rudolph
Cheers for the re-write, definatly worth keeping on the thread.
Stevo3000
A: 

Everyone has added some valid points to this discussion so I thought I'd summarise the important points.

  1. VB.net TryCast() is NOT the same as the C# as keyword. If it was the same then there would have been no need for this question in the first place.

  2. VB.net uses the If() function for ternary operations and null coalescing operations. This is not as easy to read as the C# version (? and ?? respectivly). Try to avoid the vb.net IIf() function as this is not the same.

  3. The null coalescing code pattern does not work as we cannot use the TryCast() with nullable types so we have to use the terniary pattern.

  4. oReader.IsDBNull(oReader.GetOrdinal("Name")) is the best way to check if a value is DBNull.

Stevo3000