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]
String comparison involves unnecessary overhead. If all you need is to check whether the string is empty, use the Length
property.
Code snippets:
if ( str != ��)
if ( str.Length > 0) {...}
Item 2 - Prefer StringBuilder instead of string concatenation. [Performance ]
C# string is immutable, i.e., cannot be altered. When you alter a string, you are actually creating a new string causing the following:
- The code uses more memory than necessary.
- Creates more work for the garbage collector.
- Makes the code execution run slower.
Therefore, you should prefer using StringBuilder
(Append
method).
Code snippets:
String strConcat;
ArrayList arrayOfStrings = new ArrayList();
arrayOfStrings.Add("a");
arrayOfStrings.Add("b");
foreach (string s in stringContainer) {
strConcat += s;
}
StringBuilder sbConcat = new StringBuilder ();
foreach (string s in arrayOfStrings ) {
sbConcat.append(s);
}
Item 3 - Avoid Boxing and UnBoxing as much as possible. [Performance]
Boxing a value of a value-type consists of two operations:
- Allocating an object instance.
- Copying the value-type value into that instance.
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
:
Note: Collections expect object
type.
Code snippets:
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) ;
}
st obj = (st ) arr[0];
Class st { public Int i; }
Item4 - Prefer String.Equal method instead of == operator. [Performance]
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:
string s1= "code";
string s2 = "code1";
if(s1 == s2){...}
if(s1.Equals(s2)){...}
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:
ngen [options] [assemblyName |assemblyPath ]
Item6 - Prefer 'for' over 'foreach'. [Performance]
'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:
int[] arrayOfInts= new int[5];
int sum= 0;
foreach(int i arrayOfInts) {
sum+= i;
}
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:
object o = 1.3;
try
{
string str = (string)o;
}
catch(InvalidCastException ex){...}
string str = o as string;
if(null != str){...}
Item8 - Use the 'checked' keyword to avoid overflow. [Usage]
Code snippets:
short shortNum;
int i = 32768;
shortNum = (short) i;
try {
shortNum = checked((short)i);
}
catch(OverflowException efx) {}
Item9 - Use the 'is' operator instead of casting. [Usage]
Code snippets:
public class Preson{int nAge;}
static void main(object o){
try {
(Person)o.nAge = 45;
}
catch(InvalidCastException ex){...}
}
static void func(object o)
{
if ( true == (o is Person) )
{
(Person)o.nAge = 45;
}
}
Item10 - Use Explicit interface to 'hide' the implementation of an interface [Usage]
Implementing an interface explicitly 'hides' the interface methods. Visual Studio does not display the interface methods in the intellisense.
Code snippets:
Public interface IChild{
bool IsHuman();
void lie();
}
Pubic Pinocchio: IChild {
IChild.IsHuman()
{
}
public void Lie();
}
static void main()
{
Pinocchio o = new Pinocchio();
((IChild) o).IsHuman();
o.Lie();
}
Item11 - Use @ to ease the work with literal paths. [Usage]
Code snippets:
String sFilePath = �c:\\a\\b\\c.txt�;
String sFilePath = @�c:\a\b\c.txt�;
Item12 - Make your API assembly CLS Compliant. [Usage]
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) {
}
}
protected override void Finalize() {
try {
if ( true == b) {
}
}finally { base.Finalize();}
}
Item14 - Avoid the use of GC.Collect. [Garbage Collection]
The GC.Collect
method forces garbage collection of all generations.
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:
GO.Collect();
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.