C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
24: Structs vs Classes
I'll be honest with you: for about 90% of what you do in C#, a class is the right choice. It's the workhorse. But as you start building more complex systems—especially if you get into game dev, high-frequency trading, or heavy data processing—you'll run into a performance wall. That's where structs come in.
The fundamental difference is that a class is a reference type (stored on the heap) and a struct is a value type (usually stored on the stack). In plain English: a class is like a shared Google Doc—everyone has a link to the same file. A struct is like a Word document you email to someone—they get their own independent copy.
Starting with a simple Point class
Let's imagine we're building a basic 2D map system. We need a way to represent coordinates. My first instinct is usually to just throw everything in a class.
public class Point
{
public int X { get; set; }
public int Y { get; set; }
public Point(int x, int y)
{
X = x;
Y = y;
}
}
This looks fine. It works. But watch what happens when I try to move a point using a method. I'm expecting the method to calculate a new position, but I don't want it to accidentally mess with my original starting point.
var startPoint = new Point(0, 0);
MovePoint(startPoint);
Console.WriteLine($"Start is now: {startPoint.X}, {startPoint.Y}");
void MovePoint(Point p)
{
p.X += 10;
p.Y += 10;
}
// Output: Start is now: 10, 10
Wait, why did my start point move?
This is the "Reference Type" trap. When I passed startPoint into MovePoint, I didn't pass the data itself; I passed a reference (a pointer) to the object on the heap. The method then reached into that memory address and changed the values. For a coordinate, this is often not what we want. A point is a value, not an entity with a complex lifecycle.
Let's swap class for struct and see how the behavior shifts.
public struct Point
{
public int X { get; set; }
public int Y { get; set; }
public Point(int x, int y)
{
X = x;
Y = y;
}
}
If we run that same MovePoint logic now, the output is Start is now: 0, 0. Why? Because C# copied the entire value of the struct when it was passed into the method. The method modified a local copy on the stack, and the original remained untouched. This is much more predictable for small data types.
My mistake: The Mutable Struct Nightmare
Now, here is where I messed up early in my career. I thought, "Great, it's a struct, but I still want to be able to change X and Y easily." I left the properties as set. This creates a massive headache when you start using structs in lists or arrays.
If you have a List<Point> and you try to do myList[0].X = 10;, the compiler will actually throw an error. This is because the indexer returns a copy of the struct. If C# let you change the X value on that copy, the change would vanish instantly, and you'd be wondering why your data isn't updating. It's a silent bug waiting to happen.
The fix? Always make your structs immutable. Use readonly struct and init properties. If you need to change a point, you don't modify it—you create a new one.
public readonly struct Point
{
public int X { get; init; }
public int Y { get; init; }
public Point(int x, int y)
{
X = x;
Y = y;
}
}
Now, if I want to "move" a point, I'd do something like var newPoint = new Point(oldPoint.X + 10, oldPoint.Y + 10);. It's cleaner, thread-safe, and avoids the weird pitfalls of value-type copying.
When to actually use which?
- Use a Struct when: The object is small (usually under 16 bytes), logically represents a single value (like a color, a coordinate, or a complex number), and will not be changed after it's created.
- Use a Class when: The object has a "personality" or identity, needs to be inherited from, is large, or needs to be passed around and modified by multiple parts of your program.
📋 Practical Task
Refactoring the Vector2D Value Type
You are working on a physics engine. Currently, the Vector2D is implemented as a class, which is causing memory pressure (too many objects on the heap) and accidental data mutation bugs during collision calculations.
Your Task:
- Convert the
Vector2Dclass into areadonly struct. - Change the properties from
settoinitto ensure immutability. - Implement a method called
Addthat takes anotherVector2Dand returns a newVector2Drepresenting the sum of the two, rather than modifying the existing one.
// Current buggy implementation
public class Vector2D
{
public double X { get; set; }
public double Y { get; set; }
public Vector2D(double x, double y)
{
X = x;
Y = y;
}
public void Add(Vector2D other)
{
this.X += other.X;
this.Y += other.Y;
}
}There are no comments for now.