IDisposable & Using(): Dynamic Memory Allocation VS Static Memory Allocation

Cem Tuğanlı
8 min readMar 21, 2023

--

Source: Board Card Chip — Free photo on Pixabay

Computer RAM(Random Access Memory) is a limited storage space. A computer program has to use this space as efficient as possible for the best outcome. Not using RAM wisely can cost highly. To use it well, we should know the behaviour of the memory space in question.

What is Static Memory Allocation?

Usually, when a program is loaded into memory or at compile time, static memory allocation occurs in computer RAM.

Based on the size and type of the static members, the compiler allots a specific amount of memory for them when compiling a program. The values of the static members are initialized, then stored in that memory location, which is reserved for the duration of the program’s execution.

Static memory allocations don’t take place on the heap or the stack. Instead, static memory is allotted in the “static data area” a unique section of memory that is a part of the program’s data segment. Static Data Area is generally found in a computer’s RAM, but the specific location of the static data area in memory can vary depending on the operating system and compiler being used.

The data segment is where global variables, static variables, and other static data are allocated as a part of a program’s virtual address space. The program’s stack and heap, which are used for dynamic memory allocations, are different from the data segment.

The values of all static variables and data are initialized in the static data area when a program is loaded into memory. The program can then access this area at runtime because it has been mapped into the virtual address space of the program.

The static data area is not directly mapped to physical RAM memory locations because it is a component of the program’s virtual address space.

Instead, the operating system loads the required portions of the program’s virtual address space into physical memory as necessary using a method known as demand paging. Operating system performance is enhanced and physical memory is preserved as a result. The memory for initialized static variables, global variables, and constants is typically located in the data segment.

Static memory allocation cannot be dynamically changed while a program is running because it is fixed at compile time. In contrast to this, dynamic memory allocation enables runtime memory allocation and deallocation.

  1. Global Variables: These are variables that are defined outside of any function or class, and can be accessed from any part of the program. They are allocated at compile time and retain their value throughout the execution of the program.
  2. Constants: These are variables that are assigned a value that cannot be changed at runtime. They are allocated at compile time and can be used to store values that are used repeatedly throughout the program.
  3. Readonly Fields: These are variables that are assigned a value at runtime and cannot be changed thereafter. They are allocated at compile time and can be used to store values that are used repeatedly throughout the program, but that cannot be determined at compile time.
  4. Enumerations: These are a set of named constants that can be used to represent a finite set of values. They are allocated at compile time and can be used to improve the readability and maintainability of the code.
  5. Static Fields, Properties, Methods : These are defined as static and can be accessed without creating an instance of the class. They are allocated at compile time and can be used to perform operations that do not require access to instance-specific data.
  6. There are static classes(It’s all members must have “static” modifier) and Interfaces as well.

What is Dynamic Memory Allocation?

A programming technique called dynamic memory allocation is used to allocate memory on-the-fly while a program is running. Dynamic memory allocation enables the program to ask the operating system for memory as needed while the program is being executed, in contrast to static memory allocation, where the memory is allocated at compile time.

When the size of the data being processed is unknown at the time of compilation or when the size might change while the program is running, dynamic memory allocation is typically used. It enables programs to allocate memory as needed, maximizing the use of system resources and allowing programs to modify themselves in response to changing data needs.

The most frequently used functions in C and C++ programming for allocating dynamic memory are malloc, calloc, realloc, and free. With the help of these functions, a programmer can request a block of memory of a certain size, release the memory when it is no longer required, and adjust the block’s size as needed.

In programs that deal with large amounts of data, like image processing or database management systems, where the size of the data may be too large to fit into the constrained amount of static memory available at compile time, dynamic memory allocation is frequently used.

Dynamic memory allocation should be used carefully to prevent memory leaks, which happen when memory is allocated but improperly released, wasting memory and potentially causing program crashes.

The newkeyword in C# is typically used for dynamic memory allocation, which allocates memory for an object at runtime. An illustration of dynamic memory allocation in C# is shown here:

int[] arr = new int[n]; // Dynamically allocate an array of size n
for (int i = 0; i < n; i++)
{
arr[i] = i; // Initialize the array elements
}
int n = 5; // Declaring an integer variable is a dynamic memory allocation too

In this example, we first declare an integer variable n and assign it the value 5. We then dynamically allocate an integer array arr of size n using the new keyword. The array is allocated on the heap at runtime.

Next, we use a for loop to initialize the elements of the array with values from 0 to n-1. Since the array was allocated dynamically, we can access and modify its elements at runtime as needed.

Defining collections like List<> below is also a dynamic memory allocation.

List<string> myList = new List<string>(); // Dynamically allocate a listmyList.Add("apple"); // Add elements to the list
myList.Add("banana");
myList.Add("cherry");

Once a collection like this is no longer needed, we should deallocate its memory using the Dispose() method or by letting the garbage collector handle it.

The Dispose() method provided by the IDisposable interface is used to empty the memory space where the object is created when the object is no longer needed.

Here is a simple example of disposing of an object:

using System;
class MyClass : IDisposable
{
public void Dispose()
{
Console.WriteLine("Disposing MyClass");
}
}
class Program
{
static void Main()
{
MyClass myClass = new MyClass();
// Use myClass...
myClass.Dispose();
}
}

Or we can dispose of the object and unmanaged resources automatically by just implementing using() with IDisposableinterface.

For example:

using System;
class MyArray : IDisposable
{
private int[] arr;
public MyArray(int size)
{
arr = new int[size]; // Dynamically allocate an array of size 'size'
for (int i = 0; i < size; i++)
{
arr[i] = i; // Initialize the array elements
}
}
public void UseArray()
{
// Use the array ...
}
public void Dispose()
{
((IDisposable)arr).Dispose(); // Deallocate the array memory
}
}
class Program
{
static void Main()
{
using (MyArray myArray = new MyArray(5))
{
myArray.UseArray();
}
//myArray.Dispose(); could be called instead of
//writing the "using(){}" statement
}
}

To deallocate unmanaged resources and objects, the Dispose() method can be called implicitly and automatically when an object is used in a using() block. We could just call Dispose() method explicitly and manually instead of writing using() statement too, it would do the same.

The using statement works by creating a scope in which the IDisposable object is used, and then automatically calling the object's Dispose() method when the scope is exited, regardless of whether the scope is exited normally or due to an exception being thrown.

It’s important to note that using the IDisposable interface and the using statement together is the recommended and most common way to manage unmanaged resources in C# code.

The using statement is important in C# because it provides a convenient way to ensure that an object that implements the IDisposable interface is properly disposed of after it is no longer needed. This is important because IDisposable objects often represent unmanaged resources, such as file handles or network connections, that need to be explicitly released in order to avoid memory leaks and other problems.

We know that it wasn’t required to explicitly call Dispose()on the array within the myArray class’s Dispose() method. This is because when the myArray instance or object is disposed of and any resources it was using have been released as a result of the using block, the .NET runtime will automatically manage the deallocation of the array memory. To make sure they are released properly, it would be required to dispose of any unmanaged resources the myArray class might have, such as file handles or network connections, within the Dispose() method.

Here is an example:

public class MyArray : IDisposable
{
private FileStream _fileStream; // a file handle

public MyArray(string filePath)
{
_fileStream = new FileStream(filePath, FileMode.Open);
}

// implement the IDisposable interface
public void Dispose()
{
// dispose of any managed resources here
if (_fileStream != null)
{
_fileStream.Dispose();
_fileStream = null;
}
}
}
static void Main(string[] args)
{
string filePath = "example.txt";
using (MyArray myArray = new MyArray(filePath))
{
// perform operations on myArray object here
}
}

Unmanaged Resources?

In the context of .NET programming, unmanaged resources typically refer to external resources that are not managed by the .NET runtime or the garbage collector. These resources are typically native resources provided by the operating system or other third-party libraries.

Examples of unmanaged resources include:

  • File handles and streams
  • Database connections
  • Network sockets
  • Bitmaps or other graphics objects
  • Pointers to memory allocated outside of the .NET runtime

When a .NET object holds unmanaged resources, the memory used by those resources is not automatically managed by the garbage collector. Instead, the object is responsible for explicitly releasing those resources when they are no longer needed.

This Is Important:

The cleaning method Dispose(), which releases any resources kept by the object, is implemented in classes using the IDisposable interface. These resources may be managed (such as memory allocated with the new keyword) or unmanaged (such as file handles or network connections). Although dynamic memory allocation can result in an object holding memory that needs to be released, it is not the only method by that objects might hold resources that need to be cleaned up. Even if they don’t employ dynamic memory allocation, objects that store file handles or network connections could nevertheless require cleanup.

A Note On “Using()” Statement

FrmMenu menu = new FrmMenu();
menu.Show();

If you code it like the one above, the menu object will not be automatically disposed and the menu form that you opened will continue to be displayed, however, if you code it like the one below, right after the form opens it will be closed. This is because the menu form object will be terminated after going out of the using() statement’s scope.

using(FrmMenu menu = new FrmMenu())
{
menu.Show();
}

Another syntax is:

using(FrmMenu menu = new FrmMenu());
menu.Show();

A clap or a comment is much appreciated! Thank you for your time!

--

--