π Difference Between Classes, Structs, and Records
e.g., Class = DbContext, Struct = DateTime, Record = WeatherForecast in Web API
| Aspect | Class | Struct | Record (C# 9+) |
|---|---|---|---|
| Type | Reference type | Value type | Reference type (by default, record struct makes it value type) |
| Memory | Stored on heap, reference stored on stack | Stored directly on stack (usually) | Stored on heap (like class) |
| ==Default Equality== | Compares references (by default, unless overridden) | Compares values field-by-field | Compares values (structural equality built-in) |
| Inheritance | Supports inheritance | Cannot inherit (but can implement interfaces) | Supports inheritance (like classes) |
| ==Immutability== | Mutable by default | Mutable by default | Designed for immutability (with-expressions, init-only setters) |
| Performance | Slower for small data (heap allocation, GC overhead) | Faster for small/lightweight data (stack allocation) | Similar to classes (heap allocation), but concise equality & immutability make them ideal for data models |
| ==Use Case== | Complex objects, OOP with polymorphism | Small, lightweight objects (like coordinates, color, point) | Data-centric models (DTOs, records in DDD, immutable states) |
Code Examples
πΉ Class
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}- Reference type β Equality is by reference:
var p1 = new Person { Name = "Alice", Age = 30 };
var p2 = new Person { Name = "Alice", Age = 30 };
Console.WriteLine(p1 == p2); // False (different references)πΉ Struct
struct Point
{
public int X { get; set; }
public int Y { get; set; }
}- Value type β Equality is by value:
var p1 = new Point { X = 5, Y = 10 };
var p2 = new Point { X = 5, Y = 10 };
Console.WriteLine(p1.Equals(p2)); // TrueπΉ Record
public record PersonRecord(string Name, int Age);- Reference type but compares by value:
var r1 = new PersonRecord("Alice", 30);
var r2 = new PersonRecord("Alice", 30);
Console.WriteLine(r1 == r2); // True (structural equality)- With-expressions for immutability:
var r3 = r1 with { Age = 31 };
Console.WriteLine(r3); // PersonRecord { Name = Alice, Age = 31 }β Quick Rules of Thumb
- Use Class β For OOP, inheritance, polymorphism, and complex business logic.
- Use Struct β For small, lightweight objects that represent values (like
Point,DateTime). Avoid large structs (>16 bytes) because copying becomes expensive. - Use Record β For immutable, data-centric models (DTOs, events, state in DDD, serialization).
π In short:
- Class = Reference type, identity-based.
- Struct = Value type, efficient, no inheritance.
- Record = Reference type with value-based equality + immutability (best for data models).
Great one π β this is a classic interview trap: ref vs out in C#.
Letβs carefully break it down:
π Difference Between ref and out in C#
| Aspect | ref | out |
|---|---|---|
| Initialization before call | Variable must be initialized before passing. | Variable does not need to be initialized before passing. |
| Assignment inside method | Optional β method may or may not assign a new value. | Mandatory β method must assign a value before returning. |
| Purpose | Use when the method needs to read and modify the callerβs variable. | Use when the methodβs job is to output a value back to the caller. |
| Data flow | Two-way (input + output). | One-way (output only). |
| ==Common usage== | Updating existing values, performance optimizations (large structs). | ==Methods that return multiple values (before Tuple / ValueTuple).== |
Code Example: ref
using System;
class Program
{
static void DoubleValue(ref int number)
{
number = number * 2; // modifies caller's value
}
static void Main()
{
int x = 5; // must be initialized
DoubleValue(ref x);
Console.WriteLine(x); // Output: 10
}
}Code Example: out
using System;
class Program
{
static void Divide(int dividend, int divisor, out int quotient, out int remainder)
{
quotient = dividend / divisor; // must assign
remainder = dividend % divisor;
}
static void Main()
{
int q, r; // uninitialized is OK
Divide(10, 3, out q, out r);
Console.WriteLine($"Quotient = {q}, Remainder = {r}");
// Output: Quotient = 3, Remainder = 1
}
}β Summary
-
refβ variable already has a value, method may read & modify it. -
outβ variable has no value, method must initialize it before returning. π Think of it like this: -
ref= βIβm giving you my value, you can update it.β -
out= βI donβt have a value, please give me one.β
real .NET framework example (like Int32.TryParse(string, out int) which uses out)?
π Enums
An Enum (short for enumeration) is a special value type in C# that lets you define a set of named constants for better readability and maintainability.
π Instead of using raw numbers (magic numbers), you use meaningful names.
Declaring an Enum
enum Weekday
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}By default:
- Underlying type =
int - Values start from 0 and increment by 1 (
Monday = 0,Tuesday = 1, β¦).
Custom Values
You can explicitly assign values:
enum ErrorCode
{
None = 0,
NotFound = 404,
ServerError = 500,
Unauthorized = 401
}Usage Example
class Program
{
static void Main()
{
Weekday today = Weekday.Monday;
if (today == Weekday.Monday)
{
Console.WriteLine("Start of the week!");
}
// Casting to int
int dayValue = (int)Weekday.Friday;
Console.WriteLine(dayValue); // Output: 4
// Casting from int
Weekday w = (Weekday)6;
Console.WriteLine(w); // Output: Sunday
}
}Enum Methods
C# provides built-in helpers:
foreach (var day in Enum.GetValues(typeof(Weekday)))
{
Console.WriteLine(day);
}
// Output: Monday Tuesday ... SundayFlags Enum (Bitwise Enums)
- Use
[Flags]attribute for combinations of values. - Great for permissions, states, etc.
[Flags]
enum FileAccess
{
None = 0,
Read = 1,
Write = 2,
Execute = 4
}
class Program
{
static void Main()
{
FileAccess access = FileAccess.Read | FileAccess.Write;
Console.WriteLine(access); // Output: Read, Write
bool canWrite = access.HasFlag(FileAccess.Write);
Console.WriteLine(canWrite); // Output: True
}
}β Summary
- Enums = named constants (improves readability).
- Default underlying type =
int(can be changed tobyte,short, etc.). - Useful for states, categories, error codes, permissions.
- With
[Flags], enums can represent bitwise combinations.
π In short:
- Use Enums to replace magic numbers with meaningful names.
- Use Flags Enums for combinable options.
real .NET example where Enums are used (like System.DayOfWeek or ConsoleColor)?
π Converting a string to enum is very common in C#.
Here are the main ways:
π 1. Using Enum.Parse
enum Weekday
{
Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
}
class Program
{
static void Main()
{
string input = "Friday";
Weekday day = (Weekday)Enum.Parse(typeof(Weekday), input);
Console.WriteLine(day); // Output: Friday
}
}β οΈ If the string doesnβt match, it throws an exception.
π 2. Using Enum.TryParse (Safe way)
string input = "Friday";
if (Enum.TryParse<Weekday>(input, out var day))
{
Console.WriteLine(day); // Output: Friday
}
else
{
Console.WriteLine("Invalid enum value!");
}β
No exception β returns false if the string is invalid.
π 3. Case-insensitive Parsing
string input = "friday";
Weekday day = (Weekday)Enum.Parse(typeof(Weekday), input, ignoreCase: true);
Console.WriteLine(day); // Output: FridayWith TryParse:
Enum.TryParse("friday", true, out Weekday day);
Console.WriteLine(day); // Output: Fridayβ Summary
- Enum.Parse β quick but throws exception if invalid.
- Enum.TryParse β safer, preferred in production.
- Both support ignoreCase for case-insensitive parsing.
π Example:
"Friday"βWeekday.Friday"friday"(with ignoreCase) βWeekday.Friday"Funday"β fails gracefully withTryParse.
π ** Equals(object obj)**
- Used to check equality of two objects.
- Default implementation (in
object) does reference equality β two objects are equal only if they refer to the same memory location. - You can override it in your class/struct to define value-based equality.
Example:
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override bool Equals(object obj)
{
if (obj is Person other)
{
return this.Name == other.Name && this.Age == other.Age;
}
return false;
}
}Usage:
var p1 = new Person { Name = "Alice", Age = 30 };
var p2 = new Person { Name = "Alice", Age = 30 };
Console.WriteLine(p1.Equals(p2)); // True (because we overrode Equals)π ** GetHashCode()**
- Returns an integer hash code for the object.
- Used in hash-based collections like
Dictionary<TKey,TValue>,HashSet<T>. - If two objects are equal according to
Equals(), they must return the sameGetHashCode(). - But the reverse is not true β two different objects can have the same hash code (collision).
Example:
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override bool Equals(object obj)
{
if (obj is Person other)
{
return this.Name == other.Name && this.Age == other.Age;
}
return false;
}
public override int GetHashCode()
{
return HashCode.Combine(Name, Age); // .NET built-in helper
}
}Usage:
var set = new HashSet<Person>();
set.Add(new Person { Name = "Alice", Age = 30 });
set.Add(new Person { Name = "Alice", Age = 30 });
Console.WriteLine(set.Count); // 1 (because Equals + GetHashCode match)β Rules to Remember
-
If you override
Equals(), you must also overrideGetHashCode().- Otherwise, two objects considered equal may produce different hash codes, breaking collections like
Dictionary.
- Otherwise, two objects considered equal may produce different hash codes, breaking collections like
-
Consistency:
- Equal objects β same hash code.
- Unequal objects β can have same or different hash codes.
-
Use
HashCode.Combine(...)(C# 8+) or a good hash algorithm for combining multiple fields.
Real-life Analogy
Equals()β Do these two books have the same content?GetHashCode()β A numeric label on the book for quick lookup in a library.- Different books can (rarely) share a label (collision).
- But if two books are the same, they must share the same label.
π In short:
Equals()β Defines equality logic.GetHashCode()β Provides a numeric shortcut for fast lookups in hash-based collections.
π Difference Between ==, Equals(), and ReferenceEquals()
| Operator/Method | Defined In | Default Behavior | Can be Overridden? | Typical Use |
|---|---|---|---|---|
== | Operator (can be overloaded) | For reference types: compares references (same memory). For value types: compares values. | β Yes, operator overloading | Flexible equality checks |
Equals() | System.Object | Reference equality (for reference types). Value comparison (for value types like int). | β Yes (commonly overridden) | Define custom equality logic |
ReferenceEquals() | System.Object (static method) | Always checks if two references point to the same object in memory. | β No | Identity check (ignores overrides) |
1. Using ==
string s1 = "hello";
string s2 = "hello";
Console.WriteLine(s1 == s2); // True (string overrides == to compare values)- For strings:
==is overridden β compares contents. - For classes (if not overloaded): compares references.
2. Using Equals()
object o1 = "hello";
object o2 = "hello";
Console.WriteLine(o1.Equals(o2)); // True (string overrides Equals to compare content)- By default (in
object): compares reference. - But many .NET types override it for value equality (like
string,DateTime,Guid).
3. Using ReferenceEquals()
string a = "hello";
string b = string.Copy(a);
Console.WriteLine(Object.ReferenceEquals(a, b)); // False (different objects in memory)
Console.WriteLine(a == b); // True (content is same)
Console.WriteLine(a.Equals(b)); // True (content is same)- Always true only if both references point to same memory object.
- Ignores operator overloading and
Equals()overrides.
β Quick Rules
==β behaves differently for reference types vs value types (can be overloaded).Equals()β use for semantic equality (do these objects represent the same thing?).ReferenceEquals()β use for identity check (is it the exact same object?).
Real-life Analogy
==β βThese two cars look the same (depends on how we define equality).βEquals()β βDo these cars have the same VIN number (logical equality)?βReferenceEquals()β βIs this literally the same physical car in front of me?β
π In short:
- Use
==for quick comparison (can be overloaded). - Use
Equals()when defining logical equality (override in your class). - Use
ReferenceEquals()when you want to check if two variables point to the same memory object.
TimeSpan Struct -
- https://learn.microsoft.com/en-us/dotnet/api/system.timespan?view=net-9.0
- Object β ValueType β TimeSpan
TimeSpanrepresents a duration, not an absolute time.- Can be positive or negative
- Can be created using constructors or
TimeSpan.FromX()methods. - Supports addition, subtraction, and comparison.
- Used in
DateTimeoperations for time calculations.