DEV Community

Cover image for Better Performance in C#
Mohammadreza Golabvand
Mohammadreza Golabvand

Posted on

Better Performance in C#

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;
Enter fullscreen mode Exit fullscreen mode
  • 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);
Enter fullscreen mode Exit fullscreen mode

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.

from C# 10 in a Nutshell

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; }
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)