5 Basic Ways to Improve Performance in C#

by Sharee English


Sharee English

Programmers new to C# can encounter less than ideal performance and even unexpected results due to inherent behavior of the language. This article explains several ways you can improve the performance of your code by understanding some of the built-in behavior of the language itself.

1. Boxing and UnBoxing
Variables that are based on value types directly contain values. Variables of reference types (referred to as objects) store references to the actual data and should be used to define the behavior of your application. On occasion, programmers make method calls passing a value type where a reference type is expected. To handle this situation C# uses boxing. Boxing converts a value type to a reference type and consists of two operations:

Allocating an object instance (on the heap).
Copying the value-type value into that instance.

The box contains the copy of the value type object and duplicates the interfaces implemented by the boxed value type. When you need to retrieve anything from the box, a copy of the value gets created and returned, called unboxing. That’s the key concept of boxing and unboxing. A copy of the object goes in the box, and another gets created whenever you access what’s in the box. The major issue with boxing and unboxing is that it happens automatically. Every type in C#, including the intrinsic types, is derived from Object and may be implicitly cast to an object. The following code is an example of commonly used constructs that cause boxing operations with value types.

int i = 15;

int j = 25;

Console.WriteLine(“Print values: {0}, {1}”, i, j);

This may seem like a trivial amount of overhead at first glance. However, given enough of these statements scattered throughout your application, you’ll soon discover that the overhead quickly becomes substantial. In addition to the heap allocation and copy operation performed when boxing, there’s overhead in maintaining the heap-based object, such as reference tracking, heap compaction, and garbage collection. This is what the code looks like to the compiler.

int i = 15;

int j = 25;

object obj1 = i;

object obj2 = j;

Console.WriteLine(“Print values: {0}, {1}”, obj1.ToString(), obj2.ToString());

You would never write the code as listed above, but that is what you are doing by letting the compiler automatically convert from a specific value type to System.Object. You could modify the previous code to avoid the implicit boxing and unboxing by using the ToString method as shown below.

Console.WriteLine(“Print values: {0}, {1}”, i.ToString(), j.ToString());

Using arrays to store a collection of value types is a common area where boxing can hurt application performance. 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.

2. StringBuilder
In C# a string is immutable and cannot be altered. When you alter a string, you are actually creating a new string, which in turn uses more memory than necessary, creates more work for the garbage collector and makes the code execution run slower. When a string is being modified frequently it begins to be a burden on performance .This seemingly innocent example below creates three string objects.

string msg = “Your total is “;//String object 1 is created

msg += “$500 “; //String object 2 is created

msg += DateTime.Now(); //String object 3 is created

StringBuilder is a string-like object whose value is a mutable sequence of characters. The value is said to be mutable because it can be modified once it has been created by appending, removing, replacing, or inserting characters. You would modify the above code like this.

StringBuilder sb = new StringBuilder();

sb.Append(“Your total is “);

sb.Append(“$500 “);


The individual characters in the value of a StringBuilder can be accessed with the Chars property. Index positions start from zero.

3. ‘as’ versus type casting
As good programming practice we try to avoid coercing one type into another when we can. But sometimes, runtime checking is simply unavoidable. When you have to cast an object into another type you have a couple of choices: the as operator or casting. You should use the as operator whenever you can because it is safer and more efficient at runtime.

Using traditional casting you could write something like the following.

object o = new MyObject();
MyType m;

m = (MyType)o;

if (m != null)

//work with m as a MyType object



//Report null reference failure





//report the conversion failure


Instead we will use the as operator as an alternative and produce simpler and easier to read code.

object o = new MyObject();
MyType m = o as MyType;

if (m != null)

The as operator is similar to a cast operation; however, there are two advantages to using the as operator.

It makes your code more readable.

If a type mismatch occurs, the object will become null instead of throwing an exception.

Note: Using the as operator instead of casting only works for reference types.

4. Control overflow checking
Each numerical data type has a fixed upper and lower limit. When you are performing arithmetic operations on a specific type, it is very possible that you may accidentally overflow the maximum storage of the type, or underflow the minimum storage of the type (collectively referred to as overflow). Overflow checking may seem somewhat unimportant since it will not occur often. Of course, even if you don’t expect an overflow condition, it may still occur as a result of a bug. You can turn on overflow checking in your Visual Studio project but there is a performance penalty associated with overflow checking. You may not want to slow down your code with overflow checking if you are sure that overflow can never occur or that you will handle the specific examples where it may occur. It is recommended that in debug builds you can turn on overflow checking to help spot bugs and isolate issues during your testing. In the release build, turn off overflow checking.

Checked and unchecked specify whether arithmetic operations and conversions are checked for overflow. These two keywords are prefix keywords. They can apply to an expression, a statement, or a block of statements.

A checked expression is one that will throw an exception if overflow occurs during evaluation. An unchecked expression is one that will not throw an exception. The keywords “checked” and “unchecked” can be used to explicitly specify how the evaluation is to be done.

int j = checked(a * b);

int k = unchecked(a * b);

First, a checked expression only works for arithmetic which results in an integer type. Another thing to watch is that the checked/unchecked keywords have no effect when division by zero is attempted – a DivideByZeroException is always thrown. Another issue is that overflow checking works for simple arithmetic operations (addition, subtraction, and multiplication), but no overflow checking is performed on left shift operations.

The unchecked keyword suppresses overflow checking and does not raise any OverflowException. If any operation results in an overflow while using unchecked, its value is truncated and the result is returned. You should use the checked keyword when you know an overflow may occur and you want to handle the error.

5. Using readonly versus const
There are two different versions of constants in C#: the const keyword is used for compile-time constants and the readonly keyword is used for runtime constants.

A mistake that developers may make is the use of const when readonly would be more appropriate. The main issue with const is that it’s evaluated at compile-time, which means if it’s changed at a later date, you have to not only recompile that application, but all applications that reference that application. In constrast, readonly is evaluated at run-time providing you a lot more flexibility.

Here’s an overview of the differences between const and readonly in C#.



Can not be static. Can be either instance-level or static.
The value is evaluated at compile time. The value is evaluated at run time.
It is initialized at declaration only. It can be initialized in declaration or by code in the constructor. Therefore, readonly fields can have different values depending on the constructor used.1

So, const should really only be used for logical, real-world constants such as days of the week, numerical constants, etc. As far as performance goes, you actually lose a little performance using readonly, but you make up for it in the flexibility of your application.

Whether you are developing a simple Web site or are deploying an enormous client/server application, all of these options provide different ways to optimize your code. There are many other options programmers can use to gain performance. You might want to check out Writing Faster Managed Code: Know What Things Cost or Performance Considerations for more information.

1 http://weblogs.asp.net/psteele/archive/2004/01/27/63416.aspx






Sharee English (MCSD, MCAD, MCT) is the Director of Information Services at SeattlePro Enterprises, an IT training and consulting company. She started her career as a programmer, delving into Web technologies almost twenty years ago. Today she is a highly educated executive with background in software development, training, authoring, management, operations, administration and sales. Sharee holds a Master of Arts in Management (emphasis in Information Systems), a Bachelor of Science (B.S.) in Computer Science and a B.S. in Mathematics.

If you would like to provide feedback on this article, please click here.

Copyright ©2006 SeattlePro Enterprises. All rights reserved.