Effective C#
Effective C#
Effective C#
Introduction
The following set of effective C# articles contains various ways to improve your
C# code.
The Code Project, as a large developers' community, is the right place to discuss
ways to write more efficient code. This is a knowledge infrastructure that allows
us to become better developers by writing better code. I hope you post new
messages and new ways to write effective C# code.
Background
This article is built from separate items. Each item deals with a certain aspect of
efficient C# code (Performance, Usage, Garbage collector etc.) followed by code
snippets.
Contents
Item 1 - Prefer the Length property when checking string size [ Performance]
Code snippets:
//NO
if ( str != “”)//comparison of two strings {...}
//YES
if ( str.Length > 0) {...}
C# string is immutable, i.e., cannot be altered. When you alter a string, you are
actually creating a new string causing the following:
Code snippets:
//NO
String strConcat;
ArrayList arrayOfStrings = new ArrayList();
arrayOfStrings.Add("a");
arrayOfStrings.Add("b");
foreach (string s in stringContainer) {
strConcat += s;
}
//YES
StringBuilder sbConcat = new StringBuilder ();
foreach (string s in arrayOfStrings ) {
sbConcat.append(s);
}
Given the above, you should avoid Boxing as much as you can. If you intend to
work with ArrayList, for example, do not declare your data type as struct
(Value type) because ArrayList works with Object (Reference type) and every
time you add an instance of the struct or run over the container, in a loop, a
Boxing process will occur.
The following happens when you copy one of the collection items value into the
struct:
• Casting.
• Copy the value.
Code snippets:
//NO
struct st { public Int i; }
Arraylist arr = new ArrayList();
for (int i=0 ; i< count; i++) {
st s;
s.i = 4;
arr.item.add(st) ; //<- Boxing (Allocating an object instance
// + copying the value-type value into that instance)
}
st obj = (st ) arr[0]; //<- Unboxing (casting and copy)
//YES
//Decalre the data type as class.
Class st { public Int i; }
Using the string.Equals method is much faster when the strings are matched.
So if you are very keen and need special type of performance and expect that
most of the strings will be the same, use the Equal method.
Note: The differences in performance are negligible and effects only numerous
operations.
Code snippets:
Item5 - Use Native Image Generator (nGen.exe) in case of long and heavy
initialization. [Performance]
The .NET Framework runs C# assemblies using the JIT compiler. Each code that is
executed for the first time is being compiled. In case of heavy and long
initialization in your assembly, you might want to use the nGen .NET tool.
"The nGen creates a native image from a managed assembly and installs it into
the native image cache on the local computer.
Once you create a native image for an assembly, the runtime automatically uses
that native image each time it runs the assembly. You do not have to perform any
additional procedures to cause the runtime to use a native image. Running
Ngen.exe on an assembly allows the assembly to load and execute faster,
because it restores code and data structures from the native image cache rather
than generating them dynamically." (MSDN 2005)
You do not have to lose the advantage of JIT compilation, because you can call
the nGen command on the installation machine through your project setup,
using:
'foreach' and 'for' statements serve the same goal - run in loop over block of
statements.
The main differences in using the foreach statement are that you do not need to
deal with increments and with the end of the loop expression. Moreover, the
foreach statement is designed to traverse through the entire collection. One can
say that foreach is a private case of for.
In the code snippets below, we can see that both loop blocks produce the same
results, only under the hood the foreach hurts the performance. More variables
are involved and additional heavy array copy.
The foreach is far more handier to use especially for collections but if your code
runs over large collections, prefer using 'for'.
Code snippets:
//foreach
int[] arrayOfInts= new int[5];
int sum= 0;
foreach(int i arrayOfInts) {
sum+= i;
}
//for
int[] arrayOfInts= new int[1];
int sum= 0;
for(int i = 0; i < arrayOfInts.Length; i++) {
sum+= arrayOfInts[i];
}
Item7 - Prefer the ‘as’ operator instead of direct type casting. [Usage]
The 'as' operator does not throw an exception. In case of bad cast, the return
value is null.
Code snippets:
//NO
object o = 1.3;
try
{
string str = (string)o;
}
catch(InvalidCastException ex){...}
//YES
string str = o as string;
if(null != str){...}
Code snippets:
//NO
short shortNum;
int i = 32768;
shortNum = (short) i;
// problem after statment excution
// the shortNum variable has an uninitialized value,
//YES
try {
shortNum = checked((short)i); // solution
}
catch(OverflowException efx) {}
Code snippets:
//No:
static void main(object o){
try {
(Person)o.nAge = 45;
}
catch(InvalidCastException ex){...}
}
//Yes:
static void func(object o)
{
if ( true == (o is Person) )
{
(Person)o.nAge = 45;
}
}
Code snippets:
//interface definition
Public interface IChild{
bool IsHuman();
void lie();
}
//class definition
Pubic Pinocchio: IChild {
IChild.IsHuman() //explicit interface implementation
{
}
public void Lie(); //regular interface implementation
}
Code snippets:
//Old way
String sFilePath = “c:\\a\\b\\c.txt”;
//The C# way
String sFilePath = @”c:\a\b\c.txt”;
The CLS-Compliant attribute cause the compiler to check whether your public
exposed types in the assembly are CLS-Compliant.
Prefer to define the attribute for the entire assembly, especially for API. The
incentive to create a CLS compliant assembly is that any assembly written in one
of the .NET aware languages can use your assembly more efficiently because
there is no data types interoperability.
Code snippets:
using System;
[assembly:CLSCompliant(true)]
Item13 - Define destructor and implement IDisposable interface for classes that
use native resource directly. [Garbage Collection]
You should define a destructor whenever you use native code in your assembly,
i.e., use types that are not managed by the garbage collector. The compiler
changes the destructor method to Finalize method (can be seen in the MSIL
using the ILDasm.exe).
The Garbage collector marks a class with destructor as Finalized and calls the
Finalize method while destructing the object. This behavior guarantees that
your release of resources in the destructor will occur.
But, what if you want to release the resources immediately? For this purpose, you
should implement the IDisposable interface and call the Dispose method when
you want to release the object.
Note 1: Do not use destructor if your class does not use native / unmanaged
resources. If you do so, you create unnecessary work for the garbage collector.
Note 2: If you implement the IDisposable and a destructor, you should call the
Dispose method from the destructor to force the object to release resources
immediately.
Code snippets:
~my class {
if ( true == b) {
}
}
The performance is hurt during the call to GC.Collect, the application execution
is paused. Moreover, the method might cause the promotion of short lived objects
to a higher generation, i.e., those object live longer than it should in the first
place.
If you must use it in order to reclaim the maximum amount of available memory,
use the method only on generation 0.
Code snippets:
//No:
GO.Collect();
//Yes:
GC.Collect(0);
Item15 - Use StructLayout attribute for classes and structs when using COM
Interop. [COM Interop]
The attributes cause the compiler to pack the structure in sequential memory so
that it can be sent to unmanaged code correctly (unmanaged code that expects a
specific layout).
Code snippets:
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct st{
int i;
float f;
}
What next?
The part II article, will deal with COM Interop in general, and events between
managed and unmanaged code in particular.