Modern C#: Records, Structs, Pattern Matching and Extension Methods
Codevity Admin · 10/25/2026
Step 9 of 31 · C# Interview Preparation for Beginners · Stage 3: Object-oriented programming
Codevity Admin · 10/25/2026
Interviewers increasingly ask about features added in recent versions of C#. This article covers the ones that come up most: records, structs, init and required, pattern matching, and extension methods.
A record is a type designed for data. The compiler generates value-based equality, a readable ToString(), and support for non-destructive copies with with.
var a = new Person("Asha", 31);
var b = new Person("Asha", 31);
Console.WriteLine(a == b); // True (equal by value)
Console.WriteLine(ReferenceEquals(a, b)); // False (still two objects)
var older = a with { Age = 32 }; // a copy with one property changed
Console.WriteLine(older); // Person { Name = Asha, Age = 32 }
Console.WriteLine(a); // Person { Name = Asha, Age = 31 }
public record Person(string Name, int Age);class | record (record class) | record struct | |
|---|---|---|---|
| Kind | Reference type | Reference type | Value type |
| Equality | By reference | By value | By value |
| Properties | As written | Positional ones are init-only by default | Positional ones are mutable by default |
| Best for | Behaviour and identity | Immutable data, DTOs, messages | Small immutable values |
A struct is a value type, copied on assignment. Choose a struct only when the type is:
Otherwise use a class. Large or mutable structs cause surprising copies. A struct cannot inherit from another type, but it can implement interfaces. Mark it readonly struct to guarantee it never changes.
init allows a property to be set only while the object is being created. required (C# 11) forces callers to set it:
var product = new Product { Name = "Keyboard", Price = 25m };
Console.WriteLine($"{product.Name}: {product.Price}"); // Keyboard: 25
// product.Name = "Mouse"; // compile error: init-only property
// new Product { Price = 1m }; // compile error: required member 'Name' must be set
public class Product
{
public required string Name { get; init; }
public decimal Price { get; init; }
}This gives you immutable objects with readable object-initializer syntax and compile-time protection against half-built objects.
Pattern matching tests the shape of a value and extracts parts of it in one step.
Console.WriteLine(Classify(15)); // Teenager
Console.WriteLine(Describe(42)); // integer 42
Console.WriteLine(Describe("hi")); // text of length 2
Console.WriteLine(Describe(null)); // nothing
object value = 42;
if (value is int n and > 40)
Console.WriteLine($"big int {n}"); // big int 42
static string Classify(int age) => age switch
{
< 0 => "Invalid",
< 13 => "Child",
< 20 => "Teenager",
_ => "Adult"
};
static string Describe(object? item) => item switch
{
null => "nothing",
int number => $"integer {number}",
string text => $"text of length {text.Length}",
_ => "something else"
};The common patterns: type (int number), relational (< 13), logical (and, or, not), property ({ Age: > 18 }), and list patterns ([1, 2, ..]). A switch expression must be exhaustive, so the compiler warns about a missing case.
An extension method adds a method to an existing type without changing it or inheriting from it. It is a static method in a static class whose first parameter has the this modifier:
Console.WriteLine("Hello, world".Truncate(5)); // Hello
Console.WriteLine("Hi".Truncate(5)); // Hi
public static class StringExtensions
{
public static string Truncate(this string value, int maxLength) =>
value.Length <= maxLength ? value : value[..maxLength];
}LINQ is built from extension methods on IEnumerable<T>. Remember that an extension method is only syntax: it compiles to an ordinary static call, so it cannot access private members, and an instance method with the same name always wins.
==DayOfWeekIsPalindrome(this string value)