.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# sourceIL code:throw new ArgumentNullException ("key", Environment.GetResourceString ("ArgumentNull_Key"));
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 toEnvironment.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.
Comments
Post a Comment