Archive for the ‘.NET’ Category

Refactoring in VB 2005

Well, this really, really sucks. I had been looking forward to refactoring tools quite a bit. Hopefully I will be able to weasel my way into enough side projects that I can do in C# over the next few years.

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.