C# Optimization Performance Tips

Jul 18, 2024

Before diving into optimization techniques, it’s important to identify the areas of your code that require improvement. By measuring and profiling your application’s performance, you can pinpoint the exact bottlenecks and focus your optimization efforts where they matter the most (Measure and Identify Bottlenecks).
In this blog, I’ll explain effective strategies for handling memory and reducing garbage collection overhead in your C# applications. Memory management and garbage collection are essential aspects of performance tuning in C#, so these best practices will help you optimize your code for maximum efficiency. 

Here are 8 tips that will help with performance optimization.

1. Use the IDisposable interface : 
Utilizing the IDisposable interface is a crucial C# performance tip. It helps you properly manage unmanaged resources and ensures that your application’s memory usage is efficient. 
Bad way:

public class ResourceHolder
{
    private Stream _stream;

    public ResourceHolder(string filePath)
    {
        _stream = File.OpenRead(filePath);
    }

    // Missing: IDisposable implementation
}

Good way:

public class ResourceHolder : IDisposable
{
    private Stream _stream;

    public ResourceHolder(string filePath)
    {
        _stream = File.OpenRead(filePath);
    }

    public void Dispose()
    {
        _stream?.Dispose(); // Properly disposing the unmanaged resource.
    }
}

By implementing the IDisposable interface, you ensure that unmanaged resources will be released when no longer needed, preventing memory leaks and reducing pressure on the garbage collector. This is a fundamental code optimization technique in C# that developers should utilize.

2. Asynchronous Programming with async/await 

Asynchronous programming is a powerful technique for improving C# performance in I/O-bound operations, allowing you to enhance your app’s responsiveness and efficiency. Here, we’ll explore some best practices for async/await in C#.
Limit the number of concurrent operations

Bad way:

public async Task ProcessManyItems(List<string> items)
{
    var tasks = items.Select(async item => await ProcessItem(item));
    await Task.WhenAll(tasks);
}

Good way:

public async Task ProcessManyItems(List<string> items, int maxConcurrency = 10)
{
    using (var semaphore = new SemaphoreSlim(maxConcurrency))
    {
        var tasks = items.Select(async item =>
        {
            await semaphore.WaitAsync(); // Limit concurrency by waiting for the semaphore.
            try
            {
                await ProcessItem(item);
            }
            finally
            {
                semaphore.Release(); // Release the semaphore to allow other operations.
            }
        });

        await Task.WhenAll(tasks);
    }
}

Without limiting concurrency, many tasks will run simultaneously, which can lead to heavy load and degraded overall performance. Instead, use a SemaphoreSlim to control the number of concurrent operations. 

3. UseConfigureAwait(false) when possible
ConfigureAwait(false) is a valuable C# performance trick that can help prevent deadlocks in your async code and improve efficiency by not forcing continuations to run on the original synchronization context.
 

public async Task<string> DataAsync()
{
    var data = await ReadDataAsync().ConfigureAwait(false); // Use ConfigureAwait(false) to avoid potential deadlocks.
    return ProcessData(data);
}


4. Parallel Computing and Task Parallel Library
This will help the power of multicore processors and speed up CPU-bound operations
Bad way:

private void Data(List<int> data)
{
    for (int i = 0; i < data.Count; i++)
    {
        PerformExpensiveOperation(data[i]);
    }
}

Good way:

private void Data(List<int> data)
{
    Parallel.ForEach(data, item => PerformExpensiveOperation(item));
}

Parallel loops can considerably accelerate processing of large collections by distributing the workload among multiple CPU cores. Switch from regular for and foreach loops to their parallel counterparts whenever it’s feasible and safe. 

5. Importance of Caching Data
Utilizing in-memory caching can drastically reduce time-consuming database fetches and speed up your application. The good way demonstrates the use of in-memory caching to store product data and reduce time-consuming database fetches. 

6. Optimizing LINQ Performance

  • Force immediate execution using ToList() or ToArray() when needed.
  • Use the AsParallel() extension method to ensure safety and parallelism.
  • Selecting a HashSet instead of a List offers faster look-up times and greater performance


7. Task and ValueTask for reusing asynchronous code
Use ValueTask to reduce heap allocations

public async ValueTask<string> DataAsync()
{
    var data = await ReadFromStreamAsync(_stream);
    return ProcessData(data);
}

By switching from Task<TResult> to ValueTask<TResult>, you can reduce heap allocations and ultimately improve your C# performance

8. Use HttpClientFactory to manage HttpClient instances

private readonly HttpClient _httpClient;

public MyClass(HttpClient httpClient)
{
    _httpClient = httpClient;
}

public async Task GetDataAsync()
{
    var response = await _httpClient.GetAsync("http://himashu.com/data");
}

This approach manages the lifetimes of your HttpClient instances more efficiently, preventing socket exhaustion.
- Use null-coalescing operators (??, ??=)

string datInput = NullableString() ?? "default";

- Using Span and Memory for efficient buffer management

// Using Span<T> avoids additional memory allocation and copying
byte[] data = GetData();
Span<byte> dataSpan = data.AsSpan();
ProcessData(dataSpan);

- Use StringComparison options for efficient string comparison

bool equal = string.Equals(string1, string2, StringComparison.OrdinalIgnoreCase);

- Use StringBuilder over string concatenation in loops

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
    sb.AppendFormat("Iteration: {0}", i);
}
string result = sb.ToString();

 

This has been a collection of just a few things I’ve found useful for enhancing the performance of my C# .NET code.
Remember that the key to successful development is a balance between code quality and performance optimizations. By employing these techniques, you’ll be able to build high-performing C# applications that deliver a seamless user experience.

.NET Performance Analysis: Newtonsoft.Json vs System.Text.Json
Jul 23, 2024

In the world of .NET development, handling JSON serialization and deserialization is a common task, especially when dealing with web APIs, configuration files, and data interchange between systems. Two prominent libraries for JSON processing in the .NET ecosystem are Newtonsoft.Json (often referred to simply as Newtonsoft) and System.Text.Json. In this article, we'll compare and contrast these two libraries, exploring their features, examples, advantages, and disadvantages. Newtonsoft.Json Newtonsoft.Json, developed by James Newton-King, has been the go-to library for JSON serialization and deserialization in the .NET ecosystem for many years. It offers a wide range of features and has garnered widespread adoption among developers. Let's explore some of its characteristics:   Features of Newtonsoft.Json   Flexible and Robust: Newtonsoft.Json provides comprehensive support for JSON serialization and deserialization, handling complex object graphs, custom conversions, and nullable types effortlessly. Customization Options: Developers can customize the serialization and deserialization process using attributes, custom converters, and serialization settings, allowing fine-grained control over JSON representation. Widely Adopted: Newtonsoft.Json is battle-tested and widely adopted in the .NET community, with extensive documentation, tutorials, and community support available.   using Newtonsoft.Json; // Serialization string json = JsonConvert.SerializeObject(myObject); // Deserialization MyObject obj = JsonConvert.DeserializeObject<MyObject>(json); System.Text.Json System.Text.Json, introduced in .NET Core 3.0 and later versions, is Microsoft's built-in JSON processing library, aiming to provide a modern, high-performance alternative to Newtonsoft.Json. While it may not offer the same level of features and flexibility as Newtonsoft.Json, it focuses on performance and seamless integration with the .NET ecosystem. Features of System.Text.Json Performance: System.Text.Json is optimized for performance, offering faster serialization and deserialization compared to Newtonsoft.Json in certain scenarios. Built-in Support: It seamlessly integrates with other .NET features such as async/await, streams, and memory management, making it a natural choice for .NET Core and .NET 5+ projects. Minimalistic API: System.Text.Json provides a minimalistic API surface, emphasizing simplicity and ease of use for common scenarios. using System.Text.Json; // Serialization string json = JsonSerializer.Serialize(myObject); // Deserialization MyObject obj = JsonSerializer.Deserialize<MyObject>(json);   Advantages and Disadvantages Newtonsoft.Json Advantages Comprehensive feature set with extensive customization options. Widely adopted with a large community and ecosystem. Mature and battle-tested library. Disadvantages Performance may degrade for large datasets compared to System.Text.Json. Requires additional dependencies for .NET Core and .NET 5+ projects.    System.Text.Json Advantages Optimized for performance, especially in scenarios with large datasets. Built-in support in .NET Core and .NET 5+, eliminating the need for additional dependencies. Seamless integration with other .NET features. Disadvantages Less feature-rich compared to Newtonsoft.Json, lacking some advanced customization options. Limited community support and fewer resources compared to Newtonsoft.Json. [Benchmark] public void NewtonsoftDeserializeIndividualData() { foreach (var user in serializedTestUsersList) { _ = Newtonsoft.Json.JsonConvert.DeserializeObject<User>(user); } } [Benchmark] public void MicrosoftDeserializeIndividualData() { foreach (var user in serializedTestUsersList) { _ = System.Text.Json.JsonSerializer.Deserialize<User>(user); } } Results: Data Method Count Mean  Ratio  Allocated Alloc Ratio Newtonsoft 10000 15.974 ms 1.00 35.5 MB 1.00 Microsoft 10000 8.472 ms 1.00 3.96 MB 1.0      Conclusion In the realm of JSON serialization and deserialization within the .NET landscape, our benchmarks present a compelling case. Despite claims of high performance from Newtonsoft.Json, the results unequivocally demonstrate that Microsoft’s System.Text.Json consistently outperforms its counterpart. Whether handling large or small datasets, System.Text.Json showcases superior speed and memory efficiency.

Top Useful C# .NET Snippets
Jun 07, 2024

In this blog, I will explore the top useful C# .NET snippets that every developer should have in their arsenal.  From object initialization syntax to dictionary initialization, these snippets cover a wide range of functionalities that will help you streamline your C# development process. Object Initialization Syntax - By using object initialization syntax, you can quickly create and initialize objects without the need for multiple lines of code. public class Product { public string Name { get; set; } public decimal Price { get; set; } } var product = new Product { Name = "Mouse", Price = 999.00 }; Enumerable.Range Method - This snippet simplifies the process of iterating over a range of numbers in a concise and readable manner. foreach (var number in Enumerable.Range(1, 10)) { Console.WriteLine(number); } Conditional Ternary Operator - By using the conditional ternary operator, you can streamline conditional checks and make your code more compact and readable. int time = 7; var result = (time < 5) ? "Weekend" : "Working"; Console.WriteLine(result); Task.WhenAll Method - With Task.WhenAll, you can improve the performance of your asynchronous operations by running them concurrently. async Task DownloadAllAsync(List<string> urls) { var tasks = urls.Select(url => DownloadAsync(url)).ToArray(); await Task.WhenAll(tasks); } async Task DownloadAsync(string url) { Console.WriteLine($"Downloading from {url}"); } Null-Conditional Operator - By using the null-conditional operator, you can handle null values gracefully and prevent runtime exceptions in your code. string firstName = person?.FirstName ?? "Unknown"; Console.WriteLine(firstName); LINQ Query Syntax - By leveraging LINQ query syntax, you can write complex queries on collections with ease and readability. var scores = new int[] { 90, 100, 82, 89, 92 }; var highScores = from score in scores where score >= 90 select score; foreach (var score in highScores) { Console.WriteLine(score); } Using Statement - The using statement is essential for handling disposable objects and preventing resource leaks in your code. using (var streamReader = new StreamReader(@"C:\file.txt")) { string content = streamReader.ReadToEnd(); Console.WriteLine(content); } Expression-Bodied Members - By using expression-bodied members, you can make your code more concise and expressive, especially for simple properties and methods.   public class Person { public string FirstName { get; set; } public string LastName { get; set; } public string FullName => $"{FirstName} {LastName}"; // Fullname directly dynamically set at model level. } Dictionary Initialization - Dictionary initialization simplifies the process of populating key-value pairs in a dictionary with a clean and readable syntax. var capitals = new Dictionary<string, string> { ["USA"] = "Washington, D.C.", ["Japan"] = "Tokyo", ["India"] = "Delhi" };   Appending an Element to a List - C# offers numerous methods for adding items to lists. For instance, the widely-used Add() method is available. However, there are plenty of other options as well. Here are five: // Statically defined list List<int> myList = new List<int> {2, 5, 6}; // Appending using Add() myList.Add(5); // [2, 5, 6, 5] // Appending using AddRange() myList.AddRange(new List<int> {9}); // [2, 5, 6, 5, 9] // Appending using Insert() myList.Insert(myList.Count, -4); // [2, 5, 6, 5, 9, -4] // Appending using InsertRange() myList.InsertRange(myList.Count, new List<int> {3}); // [2, 5, 6, 5, 9, -4, 3] // To Check if a List Is Empty List<int> myList = new List<int>(); // Check if a list is empty by its Count if (myList.Count == 0) { // the list is empty } // Check if a list is empty by its type flexibility **preferred method** if (!myList.Any()) { // the list is empty } String Interpolation (Formatting a String) - Oftentimes, we need to format strings to display information in a more readable or structured manner. Here are some options: string name = "Himanshu"; int age = 25; // String formatting using concatenation Console.WriteLine("My name is " + name + ", and I am " + age + " years old."); // String formatting using composite formatting Console.WriteLine("My name is {0}, and I am {1} years old.", name, age); // String formatting using interpolation (C# 6.0+) Console.WriteLine($"My name is {name}, and I am {age} years old"); These are but a small example of the power and flexibility that C# and .NET bring to the table.Thank you for reading! We hope these C# .NET snippets will help you streamline your development process and boost your productivity.

Integrate custom fonts into your PDF documents directly from your Dotnet codebase
Jun 01, 2024

To utilize custom fonts from your Dotnet codebase in HTML or PDF documents, follow these steps: Add the fonts you intend to use for your PDF or HTML documents. Ensure they are in the .ttf extension format. <PackageReference Include="Polybioz.HtmlRenderer.PdfSharp.Core" Version="1.0.0">   Include the necessary package by adding the following line to your project file: Initialize the IServiceCollection to utilize the CustomFontResolver class. You can achieve this by adding the following extension method:   public static class IServicesCollectionExtension { public static IServiceCollection InitializeDocumentProcessor(this IServiceCollection services) { GlobalFontSettings.FontResolver = new CustomFontResolver(); return services; } } Initialize the class in your program file:   builder.Services.InitializeDocumentProcessor();   Specify the DefaultFontName you wish to use. You can also manage bold and italic styles. public class CustomFontResolver : IFontResolver { string IFontResolver.DefaultFontName => "Rubik"; public FontResolverInfo ResolveTypeface(string familyName, bool isBold, bool isItalic) { if (isBold) { if (isItalic) { return new FontResolverInfo("Rubik#bi"); } return new FontResolverInfo("Rubik#b"); } if (isItalic) return new FontResolverInfo("Rubik#i"); return new FontResolverInfo("Rubik"); } public byte[] GetFont(string faceName) { switch (faceName) { case "Rubik": return CustomFontHelper.Rubik; case "Rubik#b": return CustomFontHelper.RubikBold; case "Rubik#bi": return CustomFontHelper.RubikBoldItalic; case "Rubik#i": return CustomFontHelper.RubikItalic; } return GetFont(faceName); } }   Define a helper class CustomFontHelper to facilitate loading font data. Ensure you have added the fonts for all the types you intend to use. public static class CustomFontHelper { public static byte[] Rubik { get { return LoadFontData("Rubik-Light.ttf"); } } public static byte[] RubikBold { get { return LoadFontData("Rubik-SemiBold.ttf"); } } public static byte[] RubikBoldItalic { get { return LoadFontData("Rubik-SemiBoldItalic.ttf"); } } public static byte[] RubikItalic { get { return LoadFontData("Rubik-Italic.ttf"); } } static byte[] LoadFontData(string name) { using (Stream stream = File.OpenRead("Fonts/" + name)) { if (stream == null) throw new ArgumentException("No resource with name " + name); int count = (int)stream.Length; byte[] data = new byte[count]; stream.Read(data, 0, count); return data; } } } By following these steps, you can seamlessly integrate custom fonts into your HTML and PDF documents from your Dotnet codebase, without needing to specify the font-family in the HTML directly. You can also pass font styles directly through code.

Himanshu Pranami

About the Author

Himanshu Pranami

I'm working as a Software Developer at MagnusMinds IT Solution, bringing over 3 years and 10 months of professional experience. My expertise spans a range of technologies, including the .NET Framework, .NET Core, MVC, ASP.NET, Entity Framework, ADO.NET, SQL, PostgreSQL, C#, Azure DevOps, and Microservices.