9 min read

There are many factors that negatively impact the performance of a .NET Core application. Sometimes these are minor things that were not considered earlier at the time of writing the code, and are not addressed by the accepted best practices. As a result, to solve these problems, programmers often resort to ad hoc solutions. However, when bad practices are combined together, they produce performance issues. It is always better to know the best practices that help developers write cleaner code and make the application performant.

In this article, we will learn the following topics:

  • Boxing and unboxing overhead
  • String concatenation
  • Exceptions handling
  • for versus foreach
  • Delegates
This tutorial is an extract from the book, C# 7 and .NET Core 2.0 High Performance, authored by Ovais Mehboob Ahmed Khan.

Boxing and unboxing overhead

The boxing and unboxing methods are not always good to use and they negatively impact the performance of mission-critical applications. Boxing is a method of converting a value type to an object type, and is done implicitly, whereas unboxing is a method of converting an object type back to a value type and requires explicit casting.

Let’s go through an example where we have two methods executing a loop of 10 million records, and in each iteration, they are incrementing the counter by 1. The AvoidBoxingUnboxing method is using a primitive integer to initialize and increment it on each iteration, whereas the BoxingUnboxing method is boxing by assigning the numeric value to the object type first and then unboxing it on each iteration to convert it back to the integer type, as shown in the following code:

private static void AvoidBoxingUnboxing() 
{ 
 
  Stopwatch watch = new Stopwatch(); 
  watch.Start(); 
  //Boxing  
  int counter = 0; 
  for (int i = 0; i < 1000000; i++) 
  { 
    //Unboxing 
    counter = i + 1; 
  } 
  watch.Stop(); 
  Console.WriteLine($"Time taken {watch.ElapsedMilliseconds}"); 
} 
 
private static void BoxingUnboxing() 
{ 
 
  Stopwatch watch = new Stopwatch(); 
  watch.Start(); 
  //Boxing  
  object counter = 0; 
  for (int i = 0; i < 1000000; i++) 
  { 
    //Unboxing 
    counter = (int)i + 1; 
  } 
  watch.Stop(); 
  Console.WriteLine($"Time taken {watch.ElapsedMilliseconds}"); 
}

When we run both methods, we will clearly see the differences in performance. The BoxingUnboxing is executed seven times slower than the AvoidBoxingUnboxing method, as shown in the following screenshot:

For mission-critical applications, it’s always better to avoid boxing and unboxing. However, in .NET Core, we have many other types that internally use objects and perform boxing and unboxing. Most of the types under System.Collections and System.Collections.Specialized use objects and object arrays for internal storage, and when we store primitive types in these collections, they perform boxing and convert each primitive value to an object type, adding extra overhead and negatively impacting the performance of the application. Other types of System.Data, namely DateSet, DataTable, and DataRow, also use object arrays under the hood.

Types under the System.Collections.Generic namespace or typed arrays are the best approaches to use when performance is the primary concern. For example, HashSet<T>, LinkedList<T>, and List<T> are all types of generic collections.

For example, here is a program that stores the integer value in ArrayList:

private static void AddValuesInArrayList() 
{ 
 
  Stopwatch watch = new Stopwatch(); 
  watch.Start(); 
  ArrayList arr = new ArrayList(); 
  for (int i = 0; i < 1000000; i++) 
  { 
    arr.Add(i); 
  } 
  watch.Stop(); 
  Console.WriteLine($"Total time taken is 
  {watch.ElapsedMilliseconds}"); 
}

Let’s write another program that uses a generic list of the integer type:

private static void AddValuesInGenericList() 
{ 
 
  Stopwatch watch = new Stopwatch(); 
  watch.Start(); 
  List<int> lst = new List<int>(); 
  for (int i = 0; i < 1000000; i++) 
  { 
    lst.Add(i); 
  } 
  watch.Stop(); 
  Console.WriteLine($"Total time taken is 
  {watch.ElapsedMilliseconds}"); 
}

When running both programs, the differences are pretty noticeable. The code with the generic list List<int> is over 10 times faster than the code with ArrayList. The result is as follows:

String concatenation

In .NET, strings are immutable objects. Two strings refer to the same memory on the heap until the string value is changed. If any of the string is changed, a new string is created on the heap and is allocated a new memory space. Immutable objects are generally thread safe and eliminate the race conditions between multiple threads. Any change in the string value creates and allocates a new object in memory and avoids producing conflicting scenarios with multiple threads.

For example, let’s initialize the string and assign the Hello World value to the  a string variable:

String a = "Hello World";

Now, let’s assign the  a string variable to another variable, b:

String b = a;

Both a and b point to the same value on the heap, as shown in the following diagram:

hello world

Now, suppose we change the value of b to Hope this helps:

b= "Hope this helps";

This will create another object on the heap, where a points to the same and b refers to the new memory space that contains the new text:

With each change in the string, the object allocates a new memory space. In some cases, it may be an overkill scenario, where the frequency of string modification is higher and each modification is allocated a separate memory space, creates work for the garbage collector in collecting the unused objects and freeing up space. In such a scenario, it is highly recommended that you use the StringBuilder class.

Exception handling

Improper handling of exceptions also decreases the performance of an application. The following list contains some of the best practices in dealing with exceptions in .NET Core:

  • Always use a specific exception type or a type that can catch the exception for the code you have written in the method. Using the Exception type for all cases is not a good practice.
  • It is always a good practice to use try, catch, and finally block where the code can throw exceptions. The final block is usually used to clean up the resources, and returns a proper response that the calling code is expecting.
  • In deeply nested code, don’t use try catch block and handle it to the calling method or main method. Catching exceptions on multiple stacks slows down performance and is not recommended.
  • Always use exceptions for fatal conditions that terminate the program.
  • Using exceptions for noncritical conditions, such as converting the value to an integer or reading the value from an empty array, is not recommended and should be handled through custom logic. For example, converting a string value to the integer type can be done by using the Int32.Parse method rather than by using the Convert.ToInt32 method and then failing at a point when the string is not represented as a digit.
  • While throwing an exception, add a meaningful message so that the user knows where that exception has actually occurred rather than going through the stack trace. For example, the following code shows a way of throwing an exception and adding a custom message based on the method and class being called:
static string GetCountryDetails(Dictionary<string, string> countryDictionary, string key)
{
  try
  {
    return countryDictionary[key];
  }
  catch (KeyNotFoundException ex)
  {
    KeyNotFoundException argEx = new KeyNotFoundException("
    Error occured while executing GetCountryDetails method. 
    Cause: Key not found", ex);
    throw argEx;
  }
}
  • Throw exceptions rather than returning the custom messages or error codes and handle it in the main calling method.
  • When logging exceptions, always check the inner exception and read the exception message or stack trace. It is helpful, and gives the actual point in the code where the error is thrown.

For vs foreach

For and foreach are two of the alternative ways of iterating over a list of items. Each of them operates in a different way. The for loop actually loads all the items of the list in memory first and then uses an indexer to iterate over each element, whereas foreach uses an enumerator and iterates until it reaches the end of the list.

The following table shows the types of collections that are good to use for for and foreach:

Type For/Foreach
Typed array Good for both
Array list Better with for
Generic collections Better with for

Delegates

Delegates are a type in .NET which hold the reference to the method. The type is equivalent to the function pointer in C or C++. When defining a delegate, we can specify both the parameters that the method can take and its return type. This way, the reference methods will have the same signature.

Here is a simple delegate that takes a string and returns an integer:

delegate int Log(string n);

Now, suppose we have a LogToConsole method that has the same signature as the one shown in the following code. This method takes the string and writes it to the console window:

static int LogToConsole(string a) { Console.WriteLine(a); 
  return 1; 
}

We can initialize and use this delegate like this:

Log logDelegate = LogToConsole; 
logDelegate ("This is a simple delegate call");

Suppose we have another method called LogToDatabase that writes the information in the database:

static int LogToDatabase(string a) 
{ 
  Console.WriteLine(a); 
  //Log to database 
  return 1; 
}

Here is the initialization of the new logDelegate instance that references the LogToDatabase method:

Log logDelegateDatabase = LogToDatabase; 
logDelegateDatabase ("This is a simple delegate call");

The preceding delegate is the representation of unicast delegates, as each instance refers to a single method. On the other hand, we can also create multicast delegates by assigning  LogToDatabase to the same LogDelegate instance, as follows:

Log logDelegate = LogToConsole; 
logDelegate += LogToDatabase; 
logDelegate("This is a simple delegate call");

The preceding code seems pretty straightforward and optimized, but under the hood, it has a huge performance overhead. In .NET, delegates are implemented by a MutlicastDelegate class that is optimized to run unicast delegates. It stores the reference of the method to the target property and calls the method directly. For multicast delegates, it uses the invocation list, which is a generic list, and holds the references to each method that is added. With multicast delegates, each target property holds the reference to the generic list that contains the method and executes in sequence. However, this adds an overhead for multicast delegates and takes more time to execute.

If you liked this article and would like to learn more such techniques, grab this book, C# 7 and .NET Core 2.0 High Performance, authored by Ovais Mehboob Ahmed Khan.

Read Next:

Behavior Scripting in C# and Javascript for game developers

Exciting New Features in C# 8.0

Exploring Language Improvements in C# 7.2 and 7.3

I'm a technology enthusiast who designs and creates learning content for IT professionals, in my role as a Category Manager at Packt. I also blog about what's trending in technology and IT. I'm a foodie, an adventure freak, a beard grower and a doggie lover.

1 COMMENT

LEAVE A REPLY

Please enter your comment!
Please enter your name here