CSharp Primer
π 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 ... 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 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: 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 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.