Advanced C# Tips:Prefer Lazy Initialization for Rarely Used Objects

Lazy loading and eager loading are two contrasting approaches in managing how and when data is loaded into memory in programming, particularly relevant in the context of databases, data processing, and UI rendering. Lazy Loading In lazy loading, data is loaded only when it is required. This can enhance application performance and reduce memory usage, especially when dealing with large datasets or complex objects. For example, consider an e-commerce application that displays product details. With lazy loading, the detailed data for a product (like high-resolution images or reviews) is only loaded when a user selects that product, rather than loading all detailed data for all products at application startup. Eager Loading Conversely, eager loading fetches all necessary data in a single query upfront. This approach can be more efficient when you know all the data is needed immediately and will be used. It avoids the overhead of multiple database queries that can occur with lazy loading. Imagine a reporting application where a report needs to display various interconnected data points like user details, their transaction histories, and product information. Using eager loading, all this data is loaded at once, which can be more efficient than fetching each piece of data separately as needed. While lazy loading helps with memory and initial performance, it can lead to performance issues later due to the need for repeated data fetching. Eager loading can make the initial load slower and increase memory usage but can offer smoother performance subsequently. In web applications, lazy initialization can be used for database connections in scenarios where not every request needs to access the database. Similarly, in desktop applications, it might be applied to resource-heavy components like help systems or data analysis tools that are not always immediately required. The technical reason for this approach is that it helps in managing system resources more efficiently. By delaying the creation of objects, you reduce the application’s memory footprint and avoid performing unnecessary operations, which can be particularly beneficial in scenarios with limited resources or where the performance is critical. Example of Lazy Initialization: In this example, DataProcessor is only created when ProcessData method is called and someCondition is true. This means that if ProcessData is never called, or someCondition never becomes true, the system never bears the cost of creating a DataProcessor instance. Studies and performance tests have shown that lazy initialization can lead to reduced memory usage and faster application startup times. However, it’s essential to use it judiciously; if used improperly, it can lead to increased complexity and potential issues with thread safety in multithreaded environments. In summary, lazy initialization is a valuable technique in C# for optimizing resource usage and performance, particularly in situations where certain objects are used infrequently or their initialization is costly. It allows for more efficient use of resources and can contribute to a smoother user experience by deferring the cost of initialization to the point where it’s absolutely necessary. Suleyman Cabir Ataman, PhD

Advanced C# Tips: Avoid Excessive Inlining of Methods

Inlining methods is a technique where the compiler replaces a method call with the actual method body’s code. While inlining can speed up your program by eliminating the overhead of a method call, excessive inlining can lead to problems. This approach needs to be used carefully since you need to balance performance gains with potential drawbacks. My suggestion will be my typical suggestion, do not use unless it is really necessary. Method inlining can reduce execution time, especially in certain loops or highly repetitive code paths since it removes the overhead of the method call itself. However, excessive inlining can increase the size of the compiled binary (code bloat). Larger binaries can negatively impact performance because they take longer to load into memory and can reduce the effectiveness of CPU caches, which are designed to speed up access to frequently used instructions and data. Potential use cases for method inlining might be performance-critical applications like games, financial algorithms, or data processing applications and high-frequency loop since it can be beneficial for frequently called, short methods. It can also be used in loops that execute a vast number of times, especially those found in real-time systems or simulations, can benefit from inlining methods to reduce call overhead. The compiler typically decides automatically whether to inline a method based on its complexity and the frequency of calls. Forcing the compiler to use inline methods is done through attributes or compiler options overrides this logic. While this might give a performance boost in specific scenarios, it can cause larger executable sizes and potentially loss of overall performance due to the reasons mentioned earlier. Consider a method that calculates the sum of two integers. It’s a simple operation: Applying AggressiveInlining to this method might make sense because it’s a small, frequently used method where the overhead of calling the method might be comparable to the execution time of the method itself. Here is a downside, imagine you applied aggressive inlining to several methods within a performance-critical part of your application. Initially, it might seem to run faster. However, as the application grows, the compiled code’s size increases, potentially leading to slower startup times and reduced cache efficiency. Suleyman Cabir Ataman, PhD

Advanced C# Tips: Using Array Segments Instead of Copying Arrays

Even though it is not a very popular C# tool, the ArraySegment<T> is quite old and it was introduced in .NET Framework 2.0, which was released in 2005. It is available for developers to utilize in their applications to improve performance when dealing with partial arrays. Technically, an ArraySegment is a struct, in other words, it is a value type in C#. When you instantiate an ArraySegment<T>, you are creating a lightweight object that doesn’t have the overhead of a class, such as a reference type’s heap allocation etc. The ArraySegment<T> struct internally holds three pieces of data: a reference to the original array (_array), the position where the segment starts (_offset), and the number of elements in the segment (_count). It’s this minimalistic design that allows ArraySegment<T> to be both fast and memory-efficient. It is not he same but its working principle can be compared to pointers in C/C++. A variable holds the address for the other variable. When you use ArraySegment<T>, you do not create a new array. Instead, you’re simply pointing to an existing one while defining a specific segment into that array. This approach avoids the need for additional memory allocation, which in turn minimizes the work the garbage collector has to do, leading to better performance, particularly in memory-intensive applications. Furthermore, because ArraySegment<T> is a struct, passing it to methods doesn’t create copies of the array segment. Instead, you pass a copy of the struct itself and that is a very lightweight operation due to its small size. This makes ArraySegment<T> not only efficient for memory usage but also for passing data around within your application. Here’s an example of how you might use ArraySegment<T>: In this piece of code, instead of creating a new array for the subset, we created an ArraySegment pointing to the portion of the numbers array that we’re interested in. This is more efficient because no new array is created, and no data is copied. Suleyman Cabir Ataman, PhD

Advanced C# Tips: Reuse Objects Where Possible

Reusing objects in C# is a strategy that aligns with the principles of efficient memory management and application performance optimization. This concept is important in environments where memory resources are limited or when the cost of object creation is high. For instance mobile applications, embedded systems, or server applications handling multiple concurrent requests count as typical examples. The underlying technical reason for object reuse is to reduce the overhead related to the memory allocation and garbage collection. Every time an object is created, the .NET runtime needs to allocate space on the managed heap. As objects accumulate, the garbage collector must work harder to clean up unreferenced objects, which can lead to performance hits during collection cycles. Reusing objects can mitigate these issues by maintaining a stable number of objects, thus reducing the frequency and impact of garbage collection. In applications where objects are created and discarded frequently, object pooling is a common technique but bare in mind that it is not the only one. This involves maintaining a “pool” of pre-instantiated objects that can be recycled and reused, rather than creating new instances each time. In C# we have object pool which provides a pool of reusable objects. See Microsoft’s official documentation for the details of ObjectPool here. Let’s give a simple example below: In this example, instead of allocating a new Customer object for each iteration, the application retrieves an object from the pool and then returns it to the pool when done, ready to be reused. Object reuse not only wisely uses memory but can also contribute to reducing latency in real-time applications where the delay caused by garbage collection can lead to issues. By maintaining a steady state of memory usage and minimizing pressure on the garbage collector, applications can achieve more predictable performance. Studies and tests, such as those performed in high-load server benchmarks or real-time application profiling, consistently demonstrate the benefits of object reuse. The results often show reductions in memory usage, improvements in response times, and lower CPU usage due to reduced garbage collection activity. Reusing the HttpClient object in .NET applications is a practical strategy for optimizing resource management and improving application performance. It is efficient especially when handling multiple concurrent requests is common. Creating a new HttpClient instance for every request can cause resource exhaustion and socket exhaustion due to the high cost of TCP connections and DNS lookups. By reusing a single HttpClient instance, you can mitigate these issues, reduce latency, and avoid performance degradation caused by frequent garbage collection cycles. When an HttpClient object is reused, it maintains an open connection pool, which allows for the efficient reuse of TCP connections. This minimizes the overhead of establishing new connections and ensures that resources are utilized effectively. Here’s a practical example of reusing an HttpClient object: In this example, the HttpClient instance is created once as a static readonly field, which ensures that it is reused for all HTTP requests made by the HttpClientService. This approach leverages the benefits of connection pooling and avoids the overhead of creating and disposing of HttpClient instances repeatedly. Object reuse can be particularly beneficial in several areas in a project: These additional areas highlight the broad applicability of object reuse strategies across different aspects of software development to optimize resource management and application performance. Suleyman Cabir Ataman, PhD

Advanced C# Tips: Utilize ArrayPool for Frequent Array Allocations

ArrayPool<T> in C# is a very useful but a less known feature. It has been around since .NET 2.0 but has never been popular. In order to understand ArrayPool, we can use the analogy of a communal bike-sharing system. Just as you can pick up a bike when you need it and return it when you’re done. ArrayPool<T> lets you borrow and return arrays, reducing the need to constantly create new ones. This is useful when working with large arrays or in scenarios where arrays are frequently created and discarded. For instance high-performance computing, data processing, or handling large datasets in web services. When you use ArrayPool<T>, you are essentially reusing arrays rather than creating new ones each time. This reuse significantly reduces the workload on the garbage collector (GC). Every time a new array is created, the GC needs to track it. And eventually when it’s no longer used, clean it up. By reusing arrays, ArrayPool<T> minimizes the number of new arrays that need to be tracked and cleaned, thereby reducing GC overhead. This optimization can be very important in high-performance scenarios. For instance, in a web server handling thousands of requests per second, each needing to process some data in an array, using ArrayPool<T> can lead to significant reductions in memory usage and improved response times. Usage is so simple. You simply rent and return. Here’s a simple example of using ArrayPool<T>: In this example, an array of integers is rented from the pool, used for processing, and then returned. This avoids the need to allocate a new array for each operation. Performance tests and benchmarks have shown that using ArrayPool<T> can significantly reduce memory allocation rates and GC pressure in applications with heavy array usage, leading to better overall performance. It’s a practical approach in scenarios where arrays are a major part of the workload, and performance is a critical concern. In summary, ArrayPool<T> is a powerful feature in C# for optimizing applications that frequently use arrays. It provides a way to manage array allocations more efficiently, reducing the impact on the garbage collector and improving application performance. Suleyman Cabir Ataman, PhD

Advanced C# Tips: Consider Leveraging Bitwise Operations for Simple Calculations If Possible

Bitwise operators in C# are special tools that let you work directly with the individual bits of a number’s binary representation. If you are reading this blog post, I assume you also know that in computing world, numbers are actually stored as sequences of bits. Bitwise operators allow you to perform operations on these sequences bit by bit. They are like the switches that can flip the bits in certain patterns. They allow for a variety of operations such as flipping bits on and off, shifting them left or right, or comparing two values bit by bit. However we can’t use bitwise operators for every occasion, this is why I added the statement “if possible”. On the other hand, always bare in mind not to use over-optimization for minor gains since they add more complexity. These operators include & (bitwise AND), | (bitwise OR), ^ (bitwise XOR), ~ (bitwise NOT), << (left shift), and >> (right shift). Each of these performs a specific operation on two binary values, except for the NOT operator, which works on just one. They are used in scenarios where you need to manipulate flags, encode and decode data compactly, or when you’re performing certain mathematical operations that can be optimized by operating directly on bits. Using bitwise operations can cause significant amount performance optimization because they are among the simplest and fastest operations a computer can perform. They happen directly on the CPU level even without the need to go through a higher-level. For example, checking whether a number is odd or even using bitwise operations is way faster than using division or modulus because you only need to look at the last bit of a binary number to know if it’s odd or even. The technical reason bitwise operations are faster is that they avoid the overhead of the logical operations that involve branching and more complex logic. A bitwise AND (&) operation for example, goes through each bit of two numbers and sets the corresponding bit in the result to 1 if both bits are 1. This can be done very quickly in parallel for all bits in a CPU register, making it much faster than a sequence of conditional statements. Let’s look at some simple code examples: Using logical operators for conditional checks: Using bitwise operators for the same check: In the first example, % is the modulus operator, which involves division will be a relatively slow operation. The second example uses a bitwise AND, which is much faster as it only checks the last bit of the number. Another example could be setting or clearing specific bits that might represent features in a configuration setting: Setting a Bit with Bitwise OR: Clearing a Bit with Bitwise AND and NOT: In these examples, bitwise operations are used to manipulate individual bits within an integer to turn certain features on or off. This approach is way faster than other methods of checking and setting these conditions and is a common practice in low-level programming and systems where performance is critical. The first criticism against using bitwise operators might be sacrificing the readability. I can’t say I don’t agree with that. This is why we need to use it wisely. You may want to re-consider if using bitwise operators really makes code difficult to understand. Keep it as a tool in your toolbox but use it wisely. Don’t make your code too complicated for the sake of little gains. Suleyman Cabir Ataman, PhD

Advanced C# Tips: Use Exceptions Wisely

Exceptions in C# are definitely a great facility but we need to use them wisely. We should leave the least possible probabilities of throwing an exception since exceptions come with a cost. Exceptions simply signal that something has gone wrong in your code. The purpose of an exception is to allow your program to understand and respond unexpected problems like cleaning up resources or notifying the user. However making decisions about the program’s logic is not among the primary purposes of exceptions. Even though I am talking through C#, this is true for all programming languages having exceptions. So, using exceptions for control flow is like using a sledgehammer to swat a fly. It’s not just overkill but can also introduce new problems. The reasons why using exceptions for control flow is not advised are related to: Performance: Exception throwing and exception catching are slow processes compared to control flow statements. If it is used in a loop or frequently called code, it causes a significant reduce in the performance. Resource Usage: Handling exceptions requires a a certain amount of system resources. Overusing exceptions can lead to higher memory usage and slower execution times. Readability: The code that uses exceptions for logic flow can be difficult to understand for other developers, even for yourself. Because it combines the error handling with business logic, making the code harder to follow and maintain. Error Masking: Abuse of exceptions (I couldn’t find a better name) can make it harder to understand the difference between actual errors and flow control. This potentially masks the real issues. Bad practice: Better approach: In the bad practice example, an exception is used to decide which configuration to load. This is inefficient and not the intended use of exceptions. A better approach can be checking for the file’s existence without resorting to exception handling as part of the logic flow. Of course, File.Exists doesn’t replace try-catch. Any step interacting with an external source must be wrapped with a try-catch blog; however, checking file existing or not in advance helps redirecting the flow to the correct pattern. To sum up, use regular control statements (if, for, while, etc.) for flow control and use exceptions for real, genuine error conditions only. This approach keeps your code clear, efficient, and easy to maintain. Always aim for readability and performance in your error handling strategies, keeping exceptions in their rightful place as indicators of unexpected states, not as tools for regular program logic. Suleyman Cabir Ataman, PhD

Advanced C# Tips: Prefer Value Types Over Reference Types

In C#, understanding the difference between value types and reference types is a fundamental topic when writing efficient and performant code. The choice between using a value type (like a struct) or a reference type (like a class) can have significant implications on both memory usage and performance. The technical reasoning behind preferring value types over reference types lies in how they are managed in memory. Reference types are stored on the managed heap and come with an overhead of garbage collection. It can affect performance especially if there are many allocations and deallocations. Additionally, accessing data through a reference can be slower than accessing it directly. Value types are stored on the stack. Stack generally allows for quicker access and it has less memory overhead. The stack is a region of memory that allows for fast allocation and deallocation of space for value types, making operations on these types quicker. This is particularly beneficial in high-performance applications where the cost of garbage collection can be prohibitive. How they are structured in the memory can be seen in the graph below: However, it’s important to note that value types can be less efficient if used inappropriately. For instance, constantly copying large structs can be more expensive than dealing with references to objects. Hence, value types are preferred for small and immutable data where the overhead of copying is minimal compared to the cost of managing references and garbage collection. When a value type is used, the data is handled directly, without any additional layer of abstraction or indirection. This direct approach means operations on value types can be inlined by the compiler, which eliminates the overhead of a method call and can significantly improve performance. Moreover, because value types are not subject to garbage collection in the same way as reference types, using them can lead to fewer pauses in program execution due to garbage collection, which is critical in systems requiring high responsiveness or real-time processing. It’s also worth mentioning that in multi-threaded environments, using value types can reduce the need for synchronization mechanisms. Since value types are copied rather than referenced, each thread can work with its own copy without affecting other threads, thus avoiding concurrency issues commonly encountered with reference types. To sum up, the recommendation to prefer value types over reference types is grounded in the goal of optimizing memory usage and maximizing performance. While not a one-size-fits-all solution, understanding when and how to use value types effectively is a skill that can greatly enhance the efficiency of your C# applications. Suleyman Cabir Ataman, PhD

Advanced C# Tips: Prefer Structs for Immutable Data

Before diving into topic, I want to begin with clarifying the terminology. Immutable data refers to data whose state cannot be modified after it has been created. Once an immutable object is created, its internal state remains constant throughout its lifetime. Any attempt to modify the object’s state results in the creation of a new object with the updated state, leaving the original object unchanged. In C#, you can define your data using either classes or structs. The choice between a class and a struct can influence the behaviour and performance of your application. Classes are reference types and structs are value types. We also have records introduced in C# 9.0 and specifically for data. It deserves a separate post which I am going to write, but let’s keep this article’s scope limited to class and struct. For records in C# see Microsoft’s reference.The distinction between class and struct lies in how they are handled in memory. When you create a class, you’re making an object in the heap, and you work with references to that object. If you pass this object to a method, you’re passing a reference to it, not the actual object. This means if the object is changed in the method, it changes everywhere that reference is used. When you create a struct, it is stored directly in the location where you declare it. It is created on the stack and not on the heap. When you pass a struct to a method, you are passing the whole thing, not just a reference. This makes copying more expensive, but since there’s no reference, it’s much faster to access since you’re working directly with the data. Using structs for immutable data is efficient. Without the overhead of dynamic memory allocation (which classes require since they are reference types and live on the heap, more like a filing cabinet than the top of your desk), structs can be more performant. They are created and dealt with right at the place they are used, without the need for the garbage collector to clean them up later, which saves time and resources. Let’s consider an example of a point on a 2D plane: In this case, Point2D is a struct with X and Y coordinates. Once a Point2D is created, X and Y don’t change; they are immutable. This is an ideal scenario for using a struct instead of a class. Studies and benchmarks in performance-critical applications often show that structs can offer significant memory and speed advantages. For example, a test might reveal that an operation using structs completes faster and uses less memory than the same operation using classes. However, it is important to use structs wisely. Structs in C# can be used to represent immutable data, but there are drawbacks. When structs are passed as objects, they may need to be boxed, leading to performance overhead. Additionally, structs are allocated on the stack, which can cause memory issues if they contain a lot of data. Since structs are copied when passed as arguments or stored in collections, memory usage can increase. Unlike classes, structs cannot inherit from other structs or classes, limiting their flexibility. Also, there’s no compile-time enforcement of immutability for struct fields, which could lead to unintended mutations. Considering these limitations, using immutable classes may be preferable for managing immutable data in C#. Suleyman Cabir Ataman, PhD