Monday, January 11, 2010

.NET Framework is a surprise box!!!

Most of the time when I need to get inspiration about how to do something, I turn to the inner bits and bolts of .NET Framework, because I think that there are a few hundred men-years worth of code there, and it does have some insights.

But, some times...

I was just looking into the Dictionary<TKey, TValue> class when I came across this little bit of code:
public virtual void GetObjectData(SerializationInfo info,
StreamingContext context)
{
if (info == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info);
}
// ...
}

Ok, we don't have much here, and inside the .NET Framework there are lots and lots of helper classes to aggregate common functions and adhere to the D.R.Y. principle.

However, when I dove into the ThrowArgumentNullException method, this is what I've found:
internal static void ThrowArgumentNullException(ExceptionArgument argument)
{
throw new ArgumentNullException(GetArgumentName(argument));
}

The ExceptionArgument type is actually an enumeration, that lists the possible values for the method argument. Well, I thought that perhaps there were more than meets the eye here, but when I went through the GetArgumentName function... take a look:
internal static string GetArgumentName(ExceptionArgument argument)
{
switch (argument)
{
case ExceptionArgument.obj:
return "obj";

case ExceptionArgument.dictionary:
return "dictionary";

case ExceptionArgument.dictionaryCreationThreshold:
return "dictionaryCreationThreshold";

case ExceptionArgument.array:
return "array";

case ExceptionArgument.info:
return "info";

case ExceptionArgument.key:
return "key";

case ExceptionArgument.collection:
return "collection";

case ExceptionArgument.list:
return "list";

case ExceptionArgument.match:
return "match";

case ExceptionArgument.converter:
return "converter";

case ExceptionArgument.queue:
return "queue";

case ExceptionArgument.stack:
return "stack";

case ExceptionArgument.capacity:
return "capacity";

case ExceptionArgument.index:
return "index";

case ExceptionArgument.startIndex:
return "startIndex";

case ExceptionArgument.value:
return "value";

case ExceptionArgument.count:
return "count";

case ExceptionArgument.arrayIndex:
return "arrayIndex";

case ExceptionArgument.name:
return "name";

case ExceptionArgument.mode:
return "mode";
}
return string.Empty;
}

Ok, now you're thinking that the above function could be easily rewritten as:
internal static string GetArgumentName(ExceptionArgument argument)
{
return argument.ToString();
}

And perhaps the whole shebang of going through three different method calls could be avoided by simply writting:
public virtual void GetObjectData(SerializationInfo info,
StreamingContext context)
{
if (info == null)
{
throw new ArgumentNullException("info");
}
// ...
}

Right?!

Well that's what I thought... Until I read the source code.

Thanks to Microsoft the source code for the .NET Framework is public, so as I always like to learn, I've downloaded the full source and installed.

When I read the source for the ThrowHelper class, I've come across the following explanation:
The old way to throw an exception generates quite a lot 
IL code and assembly code.
Following is an example:

C# source

throw new ArgumentNullException("key",
Environment.GetResourceString("ArgumentNull_Key"));
IL code:
IL_0003: ldstr "key"
IL_0008: ldstr "ArgumentNull_Key"
IL_000d: call string System.Environment::GetResourceString(string)
IL_0012: newobj instance void System.ArgumentNullException::
.ctor(string,string)
IL_0017: throw

Which is 21bytes in IL.

So we want to get rid of the ldstr and call to
Environment.GetResource in IL.

In order to do that, I created two enums: ExceptionResource,
ExceptionArgument to represent the argument name and resource
name in a small integer. The source code will be changed to

ThrowHelper.ThrowArgumentNullException
(ExceptionArgument.key,
ExceptionResource.ArgumentNull_Key);

The IL code will be 7 bytes.

IL_0008: ldc.i4.4
IL_0009: ldc.i4.4
IL_000a: call void System.ThrowHelper::
ThrowArgumentNullException
(valuetype System.ExceptionArgument)
IL_000f: ldarg.0

This will also reduce the Jitted code size a lot.

After reading this I started having a little bit more respect for the guys that write this stuff.

0 Comments:

Post a Comment