Tuesday, April 5, 2011
A piece of code today broke that didn't make sense at first. It was so ridiculous that I had to post about it. This isn't a C# is better than VB.NET entry. It's a legitimate "I thought I was supposed to be able to accomplish the same thing in VB.NET as I can in C# and vice-versa" entry. No flame-wars here!
The code was similar to this:
<ItemTemplate>
<%# IIf(IsDBNull(Eval("foo")), String.Empty, Eval("foo")) %>
ItemTemplate>
This is something that I do all the time in my C# UI. The C# conditional syntax lends itself nicely to this sort of deal.
<ItemTemplate>
<%# Eval("foo") == null ? String.Empty : Eval("foo") %>
ItemTemplate>
I used to get code where developers would literally have a TextBox
or Label
in the ItemTemplate
and on RowDataBound
or ItemDataBound
cast the DataItem
to T
, analyze the value of a certain property or properties, and set the property of the TextBox
, Label
, etc... accordingly. Not cool with me. I opt for one-lining it whenever humanly possibly; which is what I tried to do earlier today. Only thing is, today I failed miserably.
I knew the code was broken because everything up until this column rendered as it should have. From this column-on everthing stopped. The results set consisted of at least 30 rows. Sweet. Since the code was in my presentation layer I couldn't really breakpoint it so I moved it to my code-behind. I found that there was a NullReferenceException
. Really? I resorted to a different method of accomplishing this task via .aspx page.
<%#IIf(String.IsNullOrEmpty(Eval("foo").ToString), "A", Eval("foo"))%>
Wow. This failed miserably. Let's try something else.
<%#IIf(Eval("foo").ToString.Length = 0, "A", Eval("foo"))%>
This worked... NOT! In case you're curious, I resorted to using "A", "B", "C" for the 3 columns so I know which conditional broke. The first one broke every time.
I know that some of you are thinking that this problem shouldn't even filter down to the UI. By this I mean others have suggested that I take care of this problem in SQL. I already tried this using COALESCE
and then with ISNULL
.
ISNULL(FooColumn, '') AS FooColumn
There's a problem with this. What if FooColumn is of type money or integer? The example above wouldn't work (I tried) when the column is of type money or some type of numeric. AFAIK, there is no way to return an empty money value aside from 0.00 (if there is PLEASE let me know!). One of the requirements for this project was that NULL are valid for fields and nothing else should be displayed. That means the following wouldn't work.
ISNULL(FooColumn, 0) AS FooColumn
This would be misleading when the data is displayed because when the customer looked at the data they would think that FooColumn's value was actually 0 when in fact the zero is nothing more than an arbitrary value.
Another "fix" would be to just call ToString
in the false
part of my IIf
statement. That works, to an extent. As soon as you introduce string formatting it won't work as it should.
I could, if I was absolutely desperate; enumerate the rows in the DataSet, check each column for NULL and replace with an empty string. Although it is a very nasty suggestion it would work.
Time for another test scenario. I created 2 console apps: 1 VB, 1 C#. Each does nothing more than declare an object
that happens to be null
as well as a SqlParameter
that sets the value depending on the null'ity of said object
.
First with the VB.
Dim foo As Object = Nothing
Dim parameter As SqlParameter = New SqlParameter()
parameter.ParameterName = "foo"
parameter.Value = IIf(foo Is Nothing, "NULL", "NOT NULL")
Console.WriteLine(IIf(foo Is Nothing, "NULL", foo.ToString))Console.WriteLine(parameter.Value)
C# equivalent.
object foo = null;
SqlParameter parameter = new SqlParameter();
parameter.ParameterName = "foo";
parameter.Value = foo == null ? "NULL" : "NOT NULL";
Console.WriteLine(foo == null ? "NULL" : foo.ToString());
Console.WriteLine(parameter.Value);
I usually have the parameter name and value taken care of in the constructor as I am a self-confessed one-liner but opted for a more blog-friendly version (i.e. no text-wrapping). Anyway, the results surprised me. Surprised in a "are you f**king kidding me?" and not "This is f**king awesome!" way. To prove that there is no bias, I created the C# version first and used Reflector to disassemble and pasted the VB.NET version into my VB.NET console app.
I ran the C# version first to make sure I wasn't getting frustrated over nothing. As expected the console output was 2 NULL's. Now time for the VB version; unhandled exception (NullReference) at line 12. Line 12 is this gem right here:
Console.WriteLine(IIf(foo Is Nothing, "NULL", foo.ToString))
Hmmm. WTF would I get an exception when I am checking for NULL? Why is foo.ToString
even being interpreted? In other words, how can foo
NOT be null
? At least that's how I looked at this.
After doing a little bit of research I have found that the IIf function will actually analyze the true and the false part of the expression. In other words, it doesn't behave anything like a conditional in C# and is merely a throwback to the VB language.
Further reading of WHY this doesn't work resulted in the fact that if IIf functioned like a C# conditional then code that was ported from VB to VB.NET would break. This is an example of how an explanation can make sense and at the same time not make any sense at all. On the one hand I can see the possible need to make code portable. On the otherhand why would you switch to VB.NET if you expect to write classic VB code? I thought the purpose of going .NET was to embrace the .NET framework, not rely on backwards compatibility.
Again I'll pose the question: Is there a way to accomplish what I am trying to do without abstracting out the functionality to a helper method consisting of a full-blown IF THEN ELSE
statement? Is there a way to one-line this functionality?
As it stands now I have created a RetrieveFooValue
function that takes my object, interprets, and returns an appropriate value. This is the best I could come up with. Again, if there's a more efficient way to do this PLEASE let me know.
<ItemTemplate>
<%# RetrieveFooValue(Eval("foo")) %>
ItemTemplate>
RetrieveFooValue
contains a full-blown IF THEN ELSE
statment.
Labels: C#