Better Performance in C#
Mohammadreza Golabvand
Posted on May 21, 2024
In this post, I aim to delve into various strategies for enhancing the performance of applications in C#.
The areas I scrutinize for performance enhancement encompass: string manipulation, pertinent design patterns, database read operations, and beneficial pointers.
String Manipulation
Modifying a string: Strings in C# are immutable. When concatenating string variables using the '+' operator, a new memory space is allocated behind the scenes, where the new value is placed, and the previous value is discarded by the Garbage Collector (GC). However, by employing StringBuilder, modifications are performed in the existing memory space, negating the need for new allocations.
Comparing two strings
The method for this operation varies depending on the situation. Below, I've outlined the optimal methods for different scenarios.
- Two string variables with short values
: The optimal method for this comparison is the Equals method.
shortStringVar1.Equals(shortStringVar2);
- A string variable with a long value and a string variable with a short value
: The optimal method for this comparison is the conventional '==' operator.
longStringVar == shortStringVar;
- Comparing two string variables for username or email verification, where case sensitivity of English letters is irrelevant: The optimal method for this comparison is the Compare method.
string.Compare(stringVar1, stringVar2, ignoreCase: true);
Pertinent Design Patterns
Design patterns were fundamentally conceived for software development to be flexible, maintainable, and reusable. However, certain design patterns also contribute to performance enhancement. I've listed these below with succinct explanations.
- Flyweight pattern: The primary objective of this pattern is to share core attributes among similar objects and reduce the number of objects in the program.
- Singleton pattern: This pattern ensures that a class has only one instance and one public access point. This pattern aids in reducing memory usage.
- Prototype pattern: This pattern allows for the creation of a copy of an object for each use, instead of creating a new instance. This results in time and memory savings.
- Builder pattern: This pattern facilitates the creation of complex objects step by step using simpler objects. This leads to more readable and structured code, thereby improving the performance of the program.
Of course, these brief explanations are not sufficient for learning these patterns. If you're interested in delving deeper into design patterns, I recommend the book 'Design Patterns in C#' by Vaskaran Sarcar or the 'C# Design Patterns' course by Kevin Dockx on the Pluralsight website.
Reading Data from the Database
When using LINQ, apply the necessary conditions as much as possible before the methods that return the query result (like .First() and .ToList() and ...). By doing so, the conditions are executed in the database and only the data meeting the necessary conditions are fetched from the database to memory, not all data. If necessary, you can append other conditions after this.
In the database, INDEX the data columns that you frequently perform search operations on.
If you are using EFCore, there are two ways to index, using Data Annotation or Fluent API
For example, if you frequently perform searches among usernames, you can use the following methods.
1. Using Data Annotation
Use [Index] above the desired property.
public class User
{
public int Id { get; set; }
[Index(IsUnique = true)]
public string Username { get; set; }
}
2- Using Fluent API:
Use the HasIndex method.
public class ApplicationDbContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasIndex(u => u.Username)
.IsUnique();
}
}
Useful Tips
The performance of 'for' and 'foreach' loops is not significantly different, and the choice between these two does not impact performance.
Using "record class" and "class" provide equivalent performance. "record" is just a syntactic sugar.
The same applies to "record struct" and "struct".
Numeric data types 'int', 'byte', and 'long' provide approximately the same performance. However, 'decimal' is resource-intensive and should only be used for business calculations and prices.
Posted on May 21, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.