πŸ”‘ Difference Between Classes, Structs, and Records

e.g., Class = DbContext, Struct = DateTime, Record = WeatherForecast in Web API

AspectClassStructRecord (C# 9+)
TypeReference typeValue typeReference type (by default, record struct makes it value type)
MemoryStored on heap, reference stored on stackStored directly on stack (usually)Stored on heap (like class)
==Default Equality==Compares references (by default, unless overridden)Compares values field-by-fieldCompares values (structural equality built-in)
InheritanceSupports inheritanceCannot inherit (but can implement interfaces)Supports inheritance (like classes)
==Immutability==Mutable by defaultMutable by defaultDesigned for immutability (with-expressions, init-only setters)
PerformanceSlower 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 polymorphismSmall, 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#

Aspectrefout
Initialization before callVariable must be initialized before passing.Variable does not need to be initialized before passing.
Assignment inside methodOptional β€” method may or may not assign a new value.Mandatory β€” method must assign a value before returning.
PurposeUse 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 flowTwo-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 ... Sunday

Flags 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 to byte, 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: Friday

With 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 with TryParse.

πŸ”‘ ** 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 same GetHashCode().
  • 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

  1. If you override Equals(), you must also override GetHashCode().

    • Otherwise, two objects considered equal may produce different hash codes, breaking collections like Dictionary.
  2. Consistency:

    • Equal objects β†’ same hash code.
    • Unequal objects β†’ can have same or different hash codes.
  3. 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/MethodDefined InDefault BehaviorCan be Overridden?Typical Use
==Operator (can be overloaded)For reference types: compares references (same memory). For value types: compares values.βœ… Yes, operator overloadingFlexible equality checks
Equals()System.ObjectReference 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.❌ NoIdentity 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 -