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.
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.
Introduction In the world of database management and querying, two commonly used methods are Language Integrated Query (LINQ) and Stored Procedures. Both serve the purpose of retrieving and manipulating data from databases, but they differ significantly in their approach and implementation. In this blog post, we'll delve into the disparities between LINQ and Stored Procedures to help you understand when to use each. 1. Conceptual Differences: - LINQ Example: var query = from p in db.Products where p.Category == "Electronics" select p; foreach (var product in query) { Console.WriteLine(product.Name); } In this LINQ example, we're querying a collection of products from a database context (`db.Products`). The LINQ query selects all products belonging to the "Electronics" category. - Stored Procedures Example: CREATE PROCEDURE GetElectronicsProducts AS BEGIN SELECT * FROM Products WHERE Category = 'Electronics' END Here, we've created a Stored Procedure named `GetElectronicsProducts` that retrieves all products in the "Electronics" category from the `Products` table. 2. Performance: - LINQ: LINQ queries are translated into SQL queries at runtime by the LINQ provider. While LINQ provides a convenient and intuitive way to query data, the performance might not always be optimal, especially for complex queries or large datasets. - Stored Procedures: Stored Procedures are precompiled and optimized on the database server, leading to potentially better performance compared to dynamically generated LINQ queries. They can leverage indexing and caching mechanisms within the database, resulting in faster execution times. 3. Maintenance and Deployment: - LINQ: LINQ queries are embedded directly within the application code, making them easier to maintain and deploy alongside the application itself. However, changes to LINQ queries often require recompilation and redeployment of the application. - Stored Procedures: Stored Procedures are maintained separately from the application code and are stored within the database. This separation of concerns allows for easier maintenance and updates to the database logic without impacting the application code. Additionally, Stored Procedures can be reused across multiple applications. 4. Security: - LINQ: LINQ queries are susceptible to SQL injection attacks if proper precautions are not taken. Parameterized LINQ queries can mitigate this risk to some extent, but developers need to be vigilant about input validation and sanitation. - Stored Procedures: Stored Procedures can enhance security by encapsulating database logic and preventing direct access to underlying tables. They provide a layer of abstraction that can restrict users' access to only the operations defined within the Stored Procedure, reducing the risk of unauthorized data access or modification. Conclusion: In summary, both LINQ and Stored Procedures offer distinct advantages and considerations when it comes to querying databases. LINQ provides a more integrated and developer-friendly approach, while Stored Procedures offer performance optimization, maintainability, and security benefits. The choice between LINQ and Stored Procedures depends on factors such as application requirements, performance considerations, and security concerns. Understanding the differences between the two methods can help developers make informed decisions when designing database interactions within their applications.