Using C# Record Types for Immutable Data Models

Introduced in C# 9.0, record types offer a concise way to create immutable data models with value-based equality. They simplify many common programming tasks when working with data-centric classes.

What Are Record Types?

Records are reference types (like classes) but with built-in functionality for representing immutable data:

// Traditional class approach
public class PersonClass
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
    
    // Requires manual implementation of equality, hash code, etc.
}

// Equivalent record
public record Person(string FirstName, string LastName);

This simple declaration creates an immutable type with:

  • Constructor that accepts all properties
  • Public, init-only properties
  • Value-based equality (compares property values, not references)
  • ToString() implementation that displays all properties
  • Deconstruction support

Benefits of Using Records

1. Immutability by Default

Records are designed for immutability, making them perfect for:

  • Domain models
  • DTOs (Data Transfer Objects)
  • API responses
  • Configuration objects
var person = new Person("John", "Doe");
// person.FirstName = "Jane"; // Compile error - properties are init-only

2. Non-Destructive Mutation with 'with' Expressions

Need to change a property? Use the 'with' expression:

var person = new Person("John", "Doe");
var updatedPerson = person with { FirstName = "Jane" };

// person still refers to "John Doe"
// updatedPerson refers to "Jane Doe"

3. Value-Based Equality

Records automatically implement value equality:

var person1 = new Person("John", "Doe");
var person2 = new Person("John", "Doe");

Console.WriteLine(person1 == person2); // True
Console.WriteLine(person1.Equals(person2)); // True

4. Easy Class Hierarchies

Records can inherit from other records:

public record Person(string FirstName, string LastName);
public record Employee(string FirstName, string LastName, string Department) 
    : Person(FirstName, LastName);

When to Use Records

Use records when:

  • You need immutable objects
  • Equality should compare values, not references
  • You're creating simple data containers
  • You need non-destructive updates with the 'with' expression

Use traditional classes when:

  • You need mutable properties
  • You need reference-based equality
  • You need more control over property implementation

Performance Considerations

While records are convenient, be aware that:

  • The 'with' expression creates a new object (memory allocation)
  • Comparing large records can be slower than reference equality

Example: API Data Model

// API response model
public record WeatherForecast(
    DateTime Date,
    int TemperatureC,
    string Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

// Usage
var forecasts = await httpClient.GetFromJsonAsync<List<WeatherForecast>>("weatherforecast");

Records are a powerful addition to C#, making it easier to create robust data models with less boilerplate code.

0
48

Related

Storing passwords as plain text is dangerous. Instead, you should hash them using a strong, slow hashing algorithm like BCrypt, which includes built-in salting and resistance to brute-force attacks.

Step 1: Install BCrypt NuGet Package

Before using BCrypt, install the BCrypt.Net-Next package:

dotnet add package BCrypt.Net-Next

or via NuGet Package Manager:

Install-Package BCrypt.Net-Next

Step 2: Hash a Password

Use BCrypt.HashPassword() to securely hash a password before storing it:

using BCrypt.Net;

string password = "mySecurePassword123";
string hashedPassword = BCrypt.HashPassword(password);

Console.WriteLine(hashedPassword); // Output: $2a$12$...

Step 3: Verify a Password

To check a user's login attempt, use BCrypt.Verify():

bool isMatch = BCrypt.Verify("mySecurePassword123", hashedPassword);
Console.WriteLine(isMatch); // Output: True

Ensuring proper hashing should be at the top of your list when it comes to building authentication systems.

2
232

Working with dates is a common requirement in many applications, and calculating the difference between two dates is a particularly frequent task.

C# provides several powerful built-in methods to handle date arithmetic efficiently. Let's explore how to calculate date differences in C#.

Using DateTime and TimeSpan

The most straightforward way to calculate the difference between two dates in C# is by using the DateTime struct and the TimeSpan class:

DateTime startDate = new DateTime(2023, 1, 1);
DateTime endDate = new DateTime(2023, 12, 31);

TimeSpan difference = endDate - startDate;

Console.WriteLine($"Total days: {difference.TotalDays}");
Console.WriteLine($"Total hours: {difference.TotalHours}");
Console.WriteLine($"Total minutes: {difference.TotalMinutes}");
Console.WriteLine($"Total seconds: {difference.TotalSeconds}");

Getting Specific Units

Sometimes you need the difference in specific units (years, months, days). The TimeSpan class doesn't directly provide years and months, since these units vary in length. Here's how to handle this:

int years = endDate.Year - startDate.Year;
int months = endDate.Month - startDate.Month;

if (months < 0)
{
    years--;
    months += 12;
}

// Adjust for day differences
if (endDate.Day < startDate.Day)
{
    months--;
    int daysInMonth = DateTime.DaysInMonth(startDate.Year, startDate.Month);
    int dayDifference = daysInMonth - startDate.Day + endDate.Day;
    Console.WriteLine($"Years: {years}, Months: {months}, Days: {dayDifference}");
}
else
{
    int dayDifference = endDate.Day - startDate.Day;
    Console.WriteLine($"Years: {years}, Months: {months}, Days: {dayDifference}");
}

Using DateTimeOffset for Time Zone Awareness

If your application needs to handle dates across different time zones, consider using DateTimeOffset:

DateTimeOffset startDateOffset = new DateTimeOffset(2023, 1, 1, 0, 0, 0, TimeSpan.FromHours(-5));
DateTimeOffset endDateOffset = new DateTimeOffset(2023, 12, 31, 0, 0, 0, TimeSpan.FromHours(1));

TimeSpan timeDifference = endDateOffset - startDateOffset;
Console.WriteLine($"Total days including time zone difference: {timeDifference.TotalDays}");

Practical Applications

Date difference calculations are useful in many scenarios:

  • Calculating age from birth date
  • Determining duration between events
  • Computing business days between dates
  • Scheduling recurring events

With these techniques, you can handle most date arithmetic requirements in your C# applications efficiently and accurately.

2
303

Reflection in C# allows you to inspect and interact with types dynamically at runtime. It is useful for scenarios like plugin systems, dependency injection, and working with unknown assemblies.

Getting Started with Reflection

To use reflection, include the System.Reflection namespace:

using System;
using System.Reflection;

Invoking a Method Dynamically

You can use reflection to call methods on an object when you don't know the method name at compile time.

class Sample
{
    public void SayHello() => Console.WriteLine("Hello from Reflection!");
}

var sample = new Sample();
MethodInfo method = typeof(Sample).GetMethod("SayHello");
method?.Invoke(sample, null);
// Output: Hello from Reflection!

Invoking Methods with Parameters

If a method requires parameters, pass them as an object array:

class MathOperations
{
    public int Add(int a, int b) => a + b;
}

var math = new MathOperations();
MethodInfo method = typeof(MathOperations).GetMethod("Add");
object result = method?.Invoke(math, new object[] { 5, 3 });
Console.WriteLine(result); // Output: 8

Working with Static Methods

For static methods, pass null as the target object:

class Utility
{
    public static string GetMessage() => "Static method called!";
}

MethodInfo method = typeof(Utility).GetMethod("GetMessage");
object result = method?.Invoke(null, null);
Console.WriteLine(result); // Output: Static method called!

Performance Considerations

  • Reflection is slower than direct method calls because it bypasses compile-time optimizations.
  • Use Delegate.CreateDelegate to improve performance when invoking frequently:
Func<int, int, int> add = (Func<int, int, int>)Delegate.CreateDelegate(
    typeof(Func<int, int, int>),
    typeof(MathOperations).GetMethod("Add")
);
Console.WriteLine(add(5, 3)); // Output: 8

Conclusion

Reflection in C# is a powerful tool for dynamic method invocation. While it introduces some performance overhead, it is invaluable in scenarios requiring runtime flexibility, such as plugins, serialization, and dynamic dependency loading.

0
93