Deconstructor in C#
Amir Doosti
.Net Software Engineer | 20+ Years of Expertise | C#, .NET, Microservices Architecture
I have often heard developers use deconstructor and destructor interchangeably while these words have completely two different meanings. In this article, I would like to talk about deconstructors in detail and have a look at its usages but let’s first have a quick look at destructors.
What is a destructor?
Destructors in C# are methods that are automatically called by the garbage collector when an object is no longer needed. They are used to perform cleanup tasks such as releasing unmanaged resources (e.g., file handles, network connections) or disposing of disposable objects.
Destructors define like a method without any parameters and modifiers. A destructor has following characteristics:
Let’s see how to use a destructor in action:
using System;
using System.IO;
class FileManager
{
private FileStream? _fileStream;
private string _filePath;
// Constructor
public FileManager(string filePath)
{
_filePath = filePath;
fileStream = new FileStream(filePath, FileMode.OpenOrCreate);
Console.WriteLine($"File '{_filePath}' opened.");
}
// Destructor (Finalizer)
~FileManager()
{
if (_fileStream != null)
{
_fileStream.Close();
Console.WriteLine($"File '{_filePath}' closed in destructor.");
}
}
// Method to write to the file
public void WriteData(string data)
{
if (_fileStream != null)
{
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(data);
_fileStream.Write(bytes, 0, bytes.Length);
Console.WriteLine("Data written to file.");
}
}
}
class Program
{
static void Main(string[] args)
{
// Create an instance of FileManager
FileManager fileManager = new FileManager("example.txt");
// Write data to the file
fileManager.WriteData("Hello, world!");
// After the Main method finishes, the destructor will be called automatically
// when the object is garbage collected
}
}
In this example ~FileManager() is a destructor and is used to close the file stream when garbage collector is going to remove an object of type FileManager.
While destructors are used for cleanup, they are often not the best choice. Instead, the IDisposable interface with a Dispose method is recommended in most cases, as it allows the developer to manage resource cleanup more explicitly and deterministically.
Best Practice is combining Dispose with a Destructor. Here is a better version of the FileManager class:
using System;
using System.IO;
class FileManager: IDisposable
{
private FileStream? _fileStream;
private string _filePath;
private bool _disposed = false; // Track whether resources have been released
// Constructor
public FileManager(string filePath)
{
_filePath = filePath;
_fileStream = new FileStream(_filePath, FileMode.OpenOrCreate);
Console.WriteLine($"File '{_filePath}' opened.");
}
// Method to write to the file
public void WriteData(string data)
{
if (_fileStream != null)
{
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(data);
_fileStream.Write(bytes, 0, bytes.Length);
Console.WriteLine("Data written to file.");
}
}
// Implement IDisposable
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // Suppress finalization since cleanup is done
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Release managed resources here
if (_fileStream != null)
{
_fileStream.Close();
_fileStream = null;
}
}
// Release unmanaged resources here (if any)
_disposed = true;
}
}
// Destructor (Finalizer)
~FileManager()
{
Dispose(true);
GC.SuppressFinalize(this); // Suppress finalization since cleanup is done
}
}
Ok, enough about destructor, and let’s move to our main topic, deconstructor.
What is a Deconstructor?
Deconstructor is introduced in C# 7. A deconstructor is a special method used to decompose an object into its individual components, which allows for unpacking or extracting values from an object instance into separate variables. This concept is useful when you want to quickly retrieve multiple properties of an object without manually accessing them one by one.
Deconstructors are not destructors (which are used for cleanup). Instead, deconstructors are for extracting or destructuring objects.
Somehow we can say that a deconstructor is the opposite of a constructor. while a constructor mostly takes a set of parameters and assigns them to fields, a deconstructor does the reverse and assigns fields back to a set of variables.
How to deine a Deconstructor?
A deconstructor has the following characteristics:
public void Deconstruct(out T1 value1, out T2 value2, ...)
{
value1 = this.Property1;
value2 = this.Property2;
// And so on...
}
Here is a sample of deconstructor:
public class Person
{
public string FirstName { get; }
public string LastName { get; }
public int Age { get; }
// Constructor
public Person(string firstName, string lastName, int age)
{
FirstName = firstName;
LastName = lastName;
Age = age;
}
// Deconstructor (Deconstruct method)
public void Deconstruct(out string firstName, out string lastName, out int age)
{
firstName = FirstName;
lastName = LastName;
age = Age;
}
}
Once a class has a deconstructor, you can use tuple-like syntax to extract values from an object:
领英推荐
var person = new Person("John", "Doe", 30);
// Deconstruct the person into individual variables
var (firstName, lastName, age) = person;
Console.WriteLine($"First Name: {firstName}, Last Name: {lastName}, Age: {age}");
This allows for a clean and readable way of extracting the values of multiple properties from an object in one step, as seen with tuple deconstruction.
Note: If you don’t need a variable, you can use discard symbol “_” to escape it like:
var (firstName, lastName, _) = person;
Note: If you want to deconstruct a type that you didn’t author it, you can use deconstructor as an extension function.
Benefits of deconstructors
A practical example
Imagine you are working on a travel planning application. You have a list of Location objects, each representing a place with a name, latitude, and longitude. You want to compute the distance between pairs of locations in the list.
Let's start by defining the Location class, including a deconstructor to make it easier to unpack the location details:
public class Location
{
public string Name { get; }
public double Latitude { get; }
public double Longitude { get; }
public Location(string name, double latitude, double longitude)
{
Name = name;
Latitude = latitude;
Longitude = longitude;
}
// Deconstructor to unpack the location details
public void Deconstruct(out string name, out double latitude, out double longitude)
{
name = Name;
latitude = Latitude;
longitude = Longitude;
}
}
Using the Deconstructor to Calculate Distances
Now, you want to calculate the distance between all pairs of locations in a list. The Haversine formula is commonly used to calculate the distance between two points on the Earth given their latitude and longitude.
Here’s how you can use the deconstructor to simplify the process:
using System;
using System.Collections.Generic;
public static void Main()
{
var locations = new List<Location>
{
new Location("New York", 40.7128, -74.0060),
new Location("Los Angeles", 34.0522, -118.2437),
new Location("Chicago", 41.8781, -87.6298),
};
// Calculate and display distances between each pair of locations
for (int i = 0; i < locations.Count; i++)
{
for (int j = i + 1; j < locations.Count; j++)
{
var (name1, lat1, lon1) = locations[i];
var (name2, lat2, lon2) = locations[j];
double distance = CalculateHaversineDistance(lat1, lon1, lat2, lon2);
Console.WriteLine($"Distance between {name1} and {name2}: {distance:F2} km");
}
}
}
// Haversine formula to calculate distance between two coordinates
public static double CalculateHaversineDistance(double lat1, double lon1, double lat2, double lon2)
{
const double R = 6371; // Radius of the Earth in kilometers
var lat = (lat2 - lat1) * Math.PI / 180.0;
var lon = (lon2 - lon1) * Math.PI / 180.0;
var a = Math.Sin(lat / 2) * Math.Sin(lat / 2) +
Math.Cos(lat1 Math.PI / 180.0) Math.Cos(lat2 Math.PI / 180.0)
Math.Sin(lon / 2) * Math.Sin(lon / 2);
var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
return R * c;
}
If you run the program, the output will show the distances between the pairs of cities:
Distance between New York and Los Angeles: 3935.75 km
Distance between New York and Chicago: 1145.41 km
Distance between Los Angeles and Chicago: 2804.66 km
Conclusion
A deconstructor in C# is a powerful feature that allows you to decompose an object into its individual properties or fields using a Deconstruct method. It provides a clean and concise way to work with object values, especially when dealing with objects that have multiple properties.