Some short notes that I took after stepping into the .NET (2.0) world.
Exception Handling
If you coming from native C++ background then it takes a while to
get used to .NET exception handling
In .NET when a method throws an exception, it simply means that it
did not do what it was supposed to do. It does not (always) mean that
there is bug. Hence throwing exceptions is not bad, its expected.
With exceptions (in .NET), error handling is not a part of the
method signature. Hence (unlike native C, C++) returning error codes is
not a part of the method signature.
With exceptions we get more robust code since they cannot be
ignored. With Win32, return codes can be ignored.
When re-throwing an exception use "throw" and not "throw e".
With the later, you lose your call stack.
"throw" ==> "rethrow" in IL
"throw e" ==> "throw" in IL
Don't use "catch (Exception e)" unless you "throw" it again
You should catch specific exceptions and only when you can
meaningfully recover
Use "using" generously. It prevents resource leaks.
Try to define your own exception type since the default may not
always match your requirements
Exceptions, when derived from another exception class, should be
marked as [Serializable]
All cleanup code should go in the finally block
Use a finally block (for all cleanup activities) even if you are
not catching any exceptions in your method.
This helps the code to cleanup even when the exception is
travelling many stack frames
When catching an exception of type X and then throwing an exception
of type Y, nest X in Y.
If you are defining a custom exception for your application, derive
it from System.ApplicationException instead of System.SystemException.
This will help you categorize exceptions when you handle them. All
exceptions of the type System.ApplicationException can be assumed to be
raised by the application code. You instantly know that these exceptions
are not coming from the .NET libraries or the runtime.
Garbage Collector
(For the curious souls) In .NET, GC is reference tracking type not
reference counting type.
Managed help consists of three types of generations, GEN0, GEN1,
GEN2.
All reference types (<85k) are initialized on the GEN0 heap
Objects that survive collection get promoted to the next
generation.
Garbage collection is triggered when GEN0 fills (~256KB).
256 because of CPU L2 cache?
Generations are dynamically sized depending on the application
memory usage patterns.
Therefore it is recommended that GC.Collect() should not be
called manually because it hinders with the dynamic estimation
process of finding the right limits for each GEN.
Large objects (>85k) automatically go in GEN2 when initialized
There is one managed heap per managed process. All threads share the
same managed heap.
Before paging out a process (if a process has been idle for a
while), the OS instructs the runtime to run GC on that process so that
when the working set reduces and less data has to be paged out.
When allocating large amounts of unmanaged memory, consider
informing the GC about it by using GC.AddMemoryPressure().
Reflection
Reflection has performance costs associated with it.
Mainly because of string lookups (comparisons) in metadata (type
definition table) to find a particular type
Since we do string lookups at runtime, it is not type safe at
compile time.
GetType() and TypeOf() functions are expensive. It is recommended
that you call them once and cache the values.
"AS" is preferred over "IS" because its faster.
Avoid using the Type.InvokeMember() function since its the slowest
way to invoke a member via reflection. In general all late-bound
invocation mechanisms have significant performance costs associated with
them. Avoid using these in parts of the code which is expected to run
fast and used repeatedly.
Generics
Earlier, with ArrayLists, there was no way to enforce a homogenous
collection. Hence compile time type safety could not be enforced.
Also required boxing/unboxing for value types which has
performance costs.
Highly recommended for algorithm reuse (for any type)
Unlike C++ templates, the referencing code does not require access
to algorithms source code.
Generic methods cannot use operators on generic variables. Big
limitation!
STATIC Max<T>(T arg1, Targ2)
{
if (arg1 > arg2) // cannot do this!!
...
}