.NET Framework is a surprise box!!!
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.