I talk to many folks who are eager to use structs because of the great performance advantages. While it is true that a struct is much quicker to allocate than a reference type (class) (somewhere around an order of magnitude quicker if all the member variables are also value types), structs can have some very serious performance problems. These include:
When passed as a parameter, the memory of a value type need to be copied onto the stack, and if a very large value type is created, the cost of this copying can be significant. Value types should ideally by around ~20 bytes in size, with ~100 bytes being a general ceiling.
A call stack is only so big, and if you have very big value types passed as parameters, you'll end up with a stack overflow exception.
The big gotcha of value types is their equality and hashcode implementations. If a value type only contains other value types, all is sweet, as a bitwise equality check and simple hashcode calculations can be used. If a value type contains reference types, things get ugly. If two value types have member variables that are reference types, and these fields have different values (i.e. the pointer that points to the reference type is different), it is possible that these reference types will have overridden their equality operator to define equality in terms of member variable sameness (System.String is the prime example of this). Therefore, value types that have reference type member variables can be Equal (because all their member variables are Equal) even if they have different bits. To accommodate this scenario, a value type that has reference type member variables needs to use reflection (which is massively slower than bitwise equality) to do the equality checks on each member variable. Because objects that are Equal must have the same hashcode, GetHashCode has the same performance problems as Equals for value types with reference types.
The end result of this is that you should always override Equals and GetHashCode on publicly visible value types if you have reference type member variable. FxCop goes further, and uses a dumbed-down (but justifiably cautious) explanation that states reflection is always used for value type equality without an explicit override. FxCop is a great tool for picking up these performance issues, and keeping an eye on its output is a good idea during the course of a development cycle.
Like most folks, I had a reasonable expectation that my Smartphone would protect me from rodents and mosquitos. Imagine my horror when I discovered that the phone came with no in-built functionality in these areas. I feel lucky that the telephony stack was included.
Luckily my good mate Nick Harrow has been busy over there in Christchurch, and he come up with
some exciting products that I expect Microsoft will adopt as standard in WM6. No more rats following me around when I have my handy Smartphone near!
One product I would like to see added to Nick's catalogue is a Smartphone Vasectomy, which could use ultrasonic waves to render a man temporarily infertile. Keep up the good work Nick!!
In the C# projects I have worked in over the last 5 years or so, I see the same mistake being made constantly, often with irritating results when it comes to traking down problems in production systems. The cause of the problem is that most C# programmers I have met doesn't know the difference between throwing an exception, and
rethrowing an exception. When an exception is thrown, the frames in the call stack are recorded the Exception.StackTrace property. This provides vital information in tracking down the root problem that caused the exception. The StackTrace property is reset every time an exception object is thrown:
try{
//code here
}
catch(Exception ex){
//some code here related to the exception
throw ex; //Reset the StackTrace to make finding the real problem harder to find
}
When the exception bubbles up to a point where it is handled via detailed logging or caught in a debugger, the StackTrace will only contain information on the stack frames up to the point where the exception is caught and then thrown again. If the exception is not logged sufficiently at each point where it is caught, it can make tracking down the root problem impossible without attaching a debugger. It is also poor practice.
There are two solutions to the problem dont catch the Exception unless you can do something meaningful with it, or, if catching if required,
rethrow the same exception. The C# syntax for rethrowing an exception is the throw keyword without an expression. The C# language specification (v1.2) states: A throw statement with no expression can be used only in a catch block, in which case that statement re-throws the exception that is currently being handled by that catch block.
The code presented above can be fixed by removing the expression after the throw statement:
try{
//code here
}
catch(Exception ex){
//some code here related to the exception
throw; //Notice the absence of the ex expression
}
At a MSIL level, a different instruction is used in the two cases. The catch block in the first code same will compile to:
catch [mscorlib]System.Exception
{
IL_000c: stloc.0
IL_000d: ldloc.0
IL_000e: throw
} // end handler
In contrast, the catch block on second code sample will compile to:
catch [mscorlib]System.Exception
{
IL_000c: stloc.0
IL_000d: rethrow
} // end handler
It is unfortunate that the C# language syntax is less clear and obvious than the MSIL.
An added benefit of not re-setting the stack trace is that the code will run faster, as the unnecessary stack capture is avoided.