Sunday, 28 August 2005

I've been lazily browsing the registry on my computer (it seems I tend to do it a lot) when I noticed a familiar name - System.Collections.Queue. Wait a minute. Isn't it a .NET class? Indeed, what I was looking at was a COM registration for a class System.Collections.Queue, pointing to mscoree.dll and mscorlib.dll as its server. It sounded like someone has kindly registered the mscorlib (parts of it anyway) with COM, and it was sitting there ready to be used. To test my hypothesis, I quickly create a simple VB script:

' CollTest.vbs
set coll = CreateObject("System.Collections.Queue")
coll.Enqueue "Bob"
coll.Enqueue "Joe"
WScript.Echo coll.Dequeue()
WScript.Echo coll.Dequeue()

When I ran it, lo and behold “Bob“ and “Joe“ were the 2 strings printed. When you stop to think about it, of course it should have worked, but nevertheless it felt as a nice surprise. After all the VB Script stock object set is quite limited and getting a free extension (.NET 1.1 is on every machine I work with) was quite welcome.

Of course I went to explore it further. From the System.Collections namespace we apparently have:

  • Queue
  • Stack
  • ArrayList
  • SortedList
  • Hashtable

I'd say it makes a rather nice addition to the existing VB script (and javascript as well) toolset. But wait, there is more...

System.IO namespace is represented by two useful classes:

  • StringWriter
  • MemoryStream

Finally, there are System.Text.StringBuilder and System.Random classes.

Why would I be interested in StringWriter (or StringBuilder)? Because frankly, VBScript memory allocator sucks. Anyone who ever tried to build a large string concatenating small ones knows that after 65536 characters performance drops drastically. Concatenating first 65536 characters one by one takes about 1.5 sec on my 3.5 GHz machine. Appending the next 65536 takes about 4 sec. The following 65536 characters take 33 sec. And you don't want to know what happens after that. Well, in case you do, appending together the string representations of numbers 1 through 100000 takes over 2 minutes.

Of course I decided to check if I could do it faster with StringWriter. But there was a catch. If you check the lsit of methods of the StringWriter class in the Visual Studio object browser, or rather the TextWriter class, from which StringWriter derives all of the Write methods, you would notice that Write() has 17 overloads, and WriteLine() has 18. Obviously you would want to use a particlular overload. How do you choose one. As it happens, the .NET framework deals with this problem in a straightforward, if inelegant way. If you have 18 overloaded methods called Write, they are exposed as Write, Write_2, Write_3 ... Write_18. How do you find out which one is which? If you look into the class browser (or Ildasm), the order of the overloads is reverse to what you see. E.g. for StringWriter we have:

  • System.IO.TextWriter.Write(string, params object[]) // Invoke as Write_17
  • System.IO.TextWriter.Write(string, object, obejct, object) // Invoke as Write_16
  • System.IO.TextWriter.Write(string, object,object) // Invoke as Write_15
  • System.IO.TextWriter.Write(string, object) // Invoke as Write_14
  • System.IO.TextWriter.Write(object) // Invoke as Write_13
  • System.IO.TextWriter.Write(string) // Invoke as Write_12
  • System.IO.TextWriter.Write(decimal) // Invoke as Write_11
  • System.IO.TextWriter.Write(double) // Invoke as Write_10
  • System.IO.TextWriter.Write(float) // Invoke as Write_9
  • System.IO.TextWriter.Write(ulong) // Invoke as Write_8
  • System.IO.TextWriter.Write(long) // Invoke as Write_7
  • System.IO.TextWriter.Write(uint) // Invoke as Write_6
  • System.IO.TextWriter.Write(int) // Invoke as Write_5
  • System.IO.TextWriter.Write(bool) // Invoke as Write_4
  • System.IO.TextWriter.Write(char[], int, int) // Invoke as Write_3
  • System.IO.TextWriter.Write(char[]) // Invoke as Write_2
  • System.IO.TextWriter.Write(char) // Invoke as Write

This means that if we want to write a string into a stringwriter, we would call:

s = “Bob“
Set wrt = CreateObject("System.IO.StringWriter")
wrt.Write_12 s

To test the performance of a StringWriter as compared to concatenating a VB string I ran the following script:

'stringtest.vbs
WScript.Echo Now
s = ""
for i = 1 to 100000
s = s & CStr(i)
next
WScript.Echo Now


set wrt = CreateObject("System.IO.StringWriter")
for i = 1 to 100000
s = CStr(i)
wrt.Write_12 s
next
s = wrt.GetStringBuilder().ToString()
WScript.Echo Now

And here are the results:

8/28/2005 1:02:14 AM
8/28/2005 1:04:07 AM
8/28/2005 1:04:08 AM

As you can see, concatenating a VB string took nearly 2 minutes, while .NET string was only a second.

But wait, there is more...

Let's take a look at the StringBuilder class. The most important feature of it (at least in the context of what we are doing here) is formatting. There are 5 AppendFormat methods:

AppendFormat ( System.IFormatProvider provider , System.String format , params object[] args )
AppendFormat ( System.String
format , params object[] args )
AppendFormat ( System.String
format , System.Object arg0 , System.Object arg1 , System.Object arg2 )
AppendFormat ( System.String format , System.Object arg0 , System.Object arg1 )
AppendFormat ( System.String
format , System.Object arg0 )

Let's see if we could easily use one of them:

'sbtest.vbs
set sb = CreateObject("System.Text.StringBuilder")
sb.AppendFormat_5 Nothing, "{0} is a {1} number" & vbCrLf, Array(1, "loneliest")
sb.AppendFormat_5 Nothing, "{0} is a {1} number" & vbCrLf, Array(2, "happiest")
WScript.Echo sb.ToString()

Prints:

1 is a loneliest number
2 is a happiest number

Weee!! We have formatting! In VBScript.

Oh, yes, did I mention it also works in ASP?

08/28/2005 01:21:02 (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  |