Archive for October, 2004

I’m Turning into a Pile of Sand

The humidity in my office dipped below 20% this week, the same as last winter. If you need a frame of reference, the Sahara desert is usually at least 25%. Some people are able to tolerate this, but I’m apparently not one of them. Especially not when I’m in the 70-80 hour per week range. I have these questions:

  1. What are the long-term health detriments associated with being DRY?
  2. Would OSHA for New York City be likely to do anything about it being DRY?
  3. Is it socially acceptable to put chap stick on your eyelids?
  4. Seriously, what the f*ck?

The “Other” DataSet Serialization Problem

There are many articles out there covering the well-known DataSet serialization performance issues. Dino Esposito had a pretty good article in MSDN magazine outlining this, and the ADO 2.0 solution to it.

There is another DataSet serialization problem which I consider to be almost as annoying, that is unfortunately not addressed in ADO 2.0. This program attempts to highlight it:

using System;
using System.Data;
using System.IO;
using System.Diagnostics;
using System.Runtime.Serialization.Formatters.Binary;

// Simple class that holds a reference into the dataset.
[Serializable]
internal class DataSetDescriptor
{

       private DataSet _data;
       private DataTable _root;

       public DataSetDescriptor(DataSet ds)
       {
              _data = ds; _root = ds.Tables[0];
       }

       // Ensures that the _root table is really the same
       // one that's in the dataset.
       public void Assert()
       {
              DataTable t = _data.Tables[0];
              Debug.Assert(object.ReferenceEquals(t, _root));
       }
}

class Program
{
       static void Main(string[] args)
       {
              DataSet ds = new DataSet();
              ds.Tables.Add();
              ds.RemotingFormat = SerializationFormat.Binary;
              DataSetDescriptor dsd = new DataSetDescriptor(ds);

              // This will (obviously) succeed.
              dsd.Assert();

              using (MemoryStream stream = new MemoryStream())
              {
                     BinaryFormatter f = new BinaryFormatter();
                     f.Serialize(stream, dsd);
                     stream.Position = 0;
                     dsd = (DataSetDescriptor)f.Deserialize(stream);
              }

              // This will (curiously) fail.
              dsd.Assert();
       }
}

The reason the second assert fails is that the _data has serialized the _root into itself, thus breaking our reference to it. The _root is serialized again by the formatter’s algorithm, so we get a copy. This image might help to understand the issue:

DataSet serialization problems

I have my own not-for-the-feint-of-heart (but transparent to developers!) solution to this, which involves remoting sinks and a bunch of surrogate classes. It doesn’t look like I will be retiring it anytime soon.

While I’m on the subject, one thing that I find simultaneously funny and depressing is that in the VB.NET version of the famous DataSetSurrogate solution in KB 829740, option strict is off. If you actually run the Visual Basic example, you will find that it is considerably slower than the default DataSet serialization as a result of late binding. It’s slower than the C# version of the program by about a factor of ten.

Attributes, WithEvents, and Backing Fields

Grahn Modulary in Kokomo, Indiana writes:

Why is only ‘booya’ printed out when Derived.Reflect() is called in this test program (below)? Shouldn’t I also get ‘raiser?’ What gives, Tito?

[Note, I omitted the ReflectMeAttribute and EventRaiser classes for clarity, they're not important].

Public Class Base

    <ReflectMe()> Protected WithEvents raiser As EventRaiser
    <ReflectMe()> Protected booya As String

    Protected asdf As String

    ' ...
End Class

Public Class Derived
    Inherits Base

    ''' <summary>
    ''' Prints out the name of all members marked with the ReflectMe() attribute.
    ''' </summary>
    Public Shared Sub Reflect()
       Dim members() As MemberInfo = GetType(Derived).GetMembers( _
           BindingFlags.NonPublic _
           Or BindingFlags.Public Or BindingFlags.Instance)

        For i As Integer = 0 To members.Length - 1
            If members(i).IsDefined(GetType(ReflectMeAttribute), True) Then
                Console.WriteLine(members(i).Name)
            End If
        Next
    End Function
End Class

Well Grahn, first of all, don’t call me “Tito.” Second, notice that if you move the Reflect method to the Base class, you’ll get two items back, but maybe not the ones you’d expect. You’ll get a private field called “_raiser.”

The problem originates with the use of the WithEvents keyword and the way it is handled by the VB compiler. The raiser field is replaced by a property of the same name, and a private backing field that together make the WithEvents work. The attribute is applied to the private field.

It’s a problem here because attributes will no longer work when inheritance is involved. You could possibly get around this if it were possible to target attributes in VB, but it’s not. This might fix it, as long as the attribute supports property targets:

<Property: ReflectMe()>

But currently the only attribute target you can specify this way in VB is Assembly.

Your example makes this look kind of lame, but one place this has bitten me in real code is in Page classes. When the ASP.NET 1.1 engine compiles a page, it derives from your codebehind class, thereby obscuring any private backing fields. It really gets crazy when you consider that the VS2003 designer will stick “WithEvents” on pretty much every field it generates, regardless of whether or not you use its functionality.

Where this REALLY becomes a problem is when you dabble in developing an attribute-based framework system for a web application (as I have). Unless your developers are way above average, you’ll find yourself struggling to explain this early and often.

I understand that there are some changes to the way that pages are compiled in ASP 2.0 that would fix this situation, but to me it’s not attacking the real problem. If anyone sees this, let me know if you’re aware of an elegant way around it that I haven’t thought of.

Visual Basic Conversion Keywords are Thunderdome

Quiz for the day. This one highlights one of the many things I don’t like about VB. What’s the output of this program?

Class TestProgram

    Private Shared Sub Main()
        Console.WriteLine(CBool(Nothing))
        Console.WriteLine(CBool(GetObject()))
        Console.WriteLine(CBool(GetString()))
    End Sub

    Private Shared Function GetString() As String
        Return Nothing
    End Function

    Private Shared Function GetObject() As Object
        Return Nothing
    End Function

End Class

Since it’s likely that nobody is reading this, I’ll tell you:

False
False

Unhandled Exception: System.InvalidCastException: Conversion from string "" to type 'Boolean' is not valid. ---> System.FormatException: Input string was not in a correct format.

Two developers I work with, one very experienced and one much newer, were both stuck on this kind of problem within a week of each other.

The sharp eyes out there probably caught on that the return type of the “get” function is what’s making the difference, which is true. But it’s certainly not obvious why that would be the case. After all, in all three cases you’re trying to convert null to a Boolean value. Looking at the MSIL gets us headed in the right direction:

.method public static void Main() cil managed
{
      .custom instance void [mscorlib]System.STAThreadAttribute::.ctor()
      .entrypoint

      // Code Size: 37 byte(s)
      .maxstack 8
      L_0000: ldc.i4.0
      L_0001: call void [mscorlib]System.Console::WriteLine(bool)
      L_0006: call object VbConsoleApp.TestProgram::GetObject()
      L_000b: call bool [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.Conversions::ToBoolean(object)
      L_0010: call void [mscorlib]System.Console::WriteLine(bool)
      L_0015: call string VbConsoleApp.TestProgram::GetString()
      L_001a: call bool [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.Conversions::ToBoolean(string)
      L_001f: call void [mscorlib]System.Console::WriteLine(bool)
      L_0024: ret
}

Notice:

  • The conversion of the first literal just gets compiled out.
  • The second two conversions are calls to different functions.

And herein lies one of the dangers of the VB conversion operators. A subtle change in what you give it can result in very different behavior. The ToBoolean(object) function will happily tell you that null==false, but the ToBoolean(string) function will disagree with a vengeance. The exception will also make it look like you gave it the empty string, when in fact you did not.

You’d probably see this if you swapped a Hashtable for a NameValueCollection, as an example. We saw it pulling the values of some checkboxes / hidden inputs out of a Request.Form collection. The other conversion operators work basically the same way, so I’m sure you’d see similar behavior.

If you think that this is nitpicky, consider that the System.Convert.ToBoolean(object) and System.Convert.ToBoolean(string) functions have consistent behavior when given null. I don’t think it’s too much to ask that CBool be held to the same standard.

When it’s my code, I avoid this stuff like the plague. These things are Thunderdome.

A hoy-hoy

Welcome to my futile attempt to educate the literate public on several issues near and dear to my heart. Here I hope to cover these and other topics:

  • The Pittsburgh Steelers are obviously going
    to win the Super Bowl this year.
  • Visual Basic is pretty annoying sometimes.
  • Boy, it’s really hot and dry in my office.

So strap in, and brace for the G’s – it’s going to be wild.

I’m not usually a fan of talking about myself, but I feel inexplicably obliged to reveal a few things to the five or six people who will see the site between now and the time I show up in the Hobo Obituaries.

I graduated from Cornell in 2002 with a degree in Computer Science. I am currently a developer for a financial services software firm in New York City. It’s not necessarily a secret which one, as I don’t intend to give away the company doubloons or post anything too subversive. But, I’m not going to go out of my way to mention it. So far I have been brutally exposed to:

  • Humidity rarely exceeding 30%.
  • MS Office integration in C++/VB6 COM.
  • Enterprise ASP.NET development, and advanced web controls.
  • Framework development for a large (-ish) development team.
  • Some .NET Remoting stuff that would make your head explode.

That will do for now. If you’re out on your bike tonight, do wear white.