Classes are the blueprints. Objects are the real things you build from them.
Think about a car. A car has a make, a model, a colour, an engine size, and a speed. It can accelerate, brake, and honk.
Now think about a specific car — your neighbour's red Tesla, for example. It has actual values for those attributes, and it can actually perform those actions.
In C#, the class is the blueprint — the description of what a car is and what it does. The object is the actual instance — your neighbour's red Tesla, with real data and real behaviour.
Classes and objects are the heart of object-oriented programming in C#. They let you model real-world things, organise code, and build applications that are easier to understand, maintain, and extend.
A class is a template or blueprint. It defines the data (fields/properties) and behaviour (methods) that objects of that class will have.
An object is an actual instance of a class — a concrete thing that exists in memory with its own state.
A class in C# is a reference type that defines a data structure and the operations that can be performed on that data. It is the fundamental building block of object-oriented programming.
An object is an instance of a class — allocated on the managed heap, with its own copy of the instance fields declared by the class.
Class = the blueprint. It exists in your source code.
Object = the building built from the blueprint. It exists in memory at runtime.
You can have one class and create many objects from it — just like one blueprint can build many houses.
Key facts:
new keyword.Without classes, your program's data and behaviour are scattered. You might have arrays of primitive values, and functions that operate on them. But as the system grows, it becomes hard to:
Classes provide a way to encapsulate data and behaviour into a single unit. This gives us:
Here's how classes and objects relate to each other and to your application:
new Car() → objectLet's trace what happens when you define a class and create objects from it.
public class Car
{
public string Model { get; set; }
public int Speed { get; private set; }
public void Accelerate()
{
Speed += 10;
}
}This defines what a Car looks like: it has a Model, a Speed, and it can Accelerate().
Car myCar = new Car();
myCar.Model = "Tesla Model 3";The new keyword allocates memory on the heap, initialises the object, and returns a reference. Now myCar points to a real Car object in memory.
myCar.Accelerate(); // Speed becomes 10
myCar.Accelerate(); // Speed becomes 20
Console.WriteLine(myCar.Speed); // 20Calling a method on the object runs the behaviour defined in the class, using the object's own data.
Car anotherCar = new Car();
anotherCar.Model = "BMW i4";
anotherCar.Accelerate(); // Speed: 10
// myCar.Speed is still 20 — independent!Each object has its own copy of the instance data. Changing one does not affect the other.
Let's build a simple Person class and create objects from it.
public class Person
{
// Fields
private string _name;
private int _age;
// Constructor
public Person(string name, int age)
{
_name = name;
_age = age;
}
// Properties
public string Name => _name;
public int Age => _age;
// Method
public void Introduce()
{
Console.WriteLine($"Hi, I'm {_name} and I'm {_age} years old.");
}
// Method with behaviour
public void HaveBirthday()
{
_age++;
Console.WriteLine($" {_name} is now {_age}!");
}
}
// Usage
Person alice = new Person("Alice", 30);
Person bob = new Person("Bob", 25);
alice.Introduce(); // Hi, I'm Alice and I'm 30 years old.
bob.Introduce(); // Hi, I'm Bob and I'm 25 years old.
alice.HaveBirthday(); // Alice is now 31!
bob.HaveBirthday(); // Bob is now 26!
Console.WriteLine($"{alice.Name} is {alice.Age}"); // Alice is 31Code → Meaning → Result
Person alice = new Person("Alice", 30); — creates a new Person object, passing values to the constructor.alice.Introduce() — calls a method on alice.alice.HaveBirthday() — modifies alice's state but not bob's.alice.Name — property access returns the value, but the field is private so it cannot be changed directly.Imagine an e-commerce system that manages orders. Each order has items, a total, and a status. Here's how you might model this:
public class OrderItem
{
public string ProductName { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal Total => Quantity * UnitPrice;
}
public class Order
{
private List<OrderItem> _items = new();
public int OrderId { get; set; }
public DateTime OrderDate { get; set; }
public string CustomerName { get; set; }
public string Status { get; private set; } = "Pending";
public IReadOnlyList<OrderItem> Items => _items;
public void AddItem(OrderItem item)
{
_items.Add(item);
}
public decimal TotalAmount => _items.Sum(i => i.Total);
public void Ship()
{
if (Status != "Pending")
throw new InvalidOperationException("Order already processed.");
Status = "Shipped";
}
public void Deliver()
{
if (Status != "Shipped")
throw new InvalidOperationException("Order must be shipped first.");
Status = "Delivered";
}
}
// Usage in a real application:
var order = new Order
{
OrderId = 1001,
OrderDate = DateTime.UtcNow,
CustomerName = "Jane Doe"
};
order.AddItem(new OrderItem { ProductName = "Laptop", Quantity = 1, UnitPrice = 1200.00m });
order.AddItem(new OrderItem { ProductName = "Mouse", Quantity = 2, UnitPrice = 25.99m });
Console.WriteLine($"Order total: {order.TotalAmount:C}"); // $1,251.98
order.Ship();
order.Deliver();
Console.WriteLine($"Status: {order.Status}"); // DeliveredIn this example:
Order encapsulates the order's state and behaviour.OrderItem is a separate class because each item is a distinct thing with its own data.IReadOnlyList.A class is like an architect's blueprint for a house. The blueprint specifies:
An object is the actual house built from that blueprint. You can build many houses from the same blueprint:
The blueprint doesn't change when you paint one house red. Each object is independent.
What actually happens inside the .NET runtime when you define a class and create objects?
class definition, it emits type metadata into the assembly.new Car() instructs the runtime to allocate memory on the managed heap.0, null, false).this reference to the object.IDisposable).This is the most common confusion. A class is the definition — it's like the text of a recipe. An object is the actual cake you bake — it exists in the real world (memory).
class vs structA class is a reference type (stored on the heap; passed by reference). A struct is a value type (stored on the stack or inline; passed by value). Classes are used for complex objects with behaviour; structs are used for small, simple data containers.
A field is a variable directly stored in the object. A property is a pair of methods (getter/setter) that control access to a field. Properties allow you to add logic (validation, notifications) while keeping the public API stable.
public class Example
{
private int _value; // field
public int Value // property
{
get => _value;
set
{
if (value < 0) throw new ArgumentException("Must be non-negative");
_value = value;
}
}
}Instance members belong to a specific object. Static members belong to the class itself — they are shared across all objects and exist even if no objects are created.
public class Counter
{
public int InstanceCount; // each object has its own
public static int TotalCreations; // shared across all objects
public Counter()
{
InstanceCount = 0;
TotalCreations++;
}
}newWrong:
Person p; // p is null — no object exists!
p.Introduce(); // NullReferenceException!Correct:
Person p = new Person("Alice", 30);
p.Introduce(); // Works!Wrong:
Console.WriteLine(Counter.TotalCreations); // OK, static
Console.WriteLine(Counter.InstanceCount); // Error! Can't access instance member from classCorrect:
var c = new Counter();
Console.WriteLine(c.InstanceCount); // OK, instance
Console.WriteLine(Counter.TotalCreations); // OK, staticWrong:
public class Order
{
public List<OrderItem> Items { get; set; } // Anyone can modify!
}Correct:
public class Order
{
private readonly List<OrderItem> _items = new();
public IReadOnlyList<OrderItem> Items => _items; // Read-only view
public void AddItem(OrderItem item) => _items.Add(item);
}== for object equality incorrectlyWrong:
Person a = new Person("Alice", 30);
Person b = new Person("Alice", 30);
Console.WriteLine(a == b); // False — different objects, even if data is sameCorrect:
Console.WriteLine(a.Name == b.Name && a.Age == b.Age); // True
// Or override Equals and == in your class for value equalityrecord) when:struct or record struct.new to create objects — then call methods and access properties.You've seen how classes define blueprints and objects are the real instances. Let's test your understanding.
1. What is the difference between a class and an object?
Correct: B
Why B is correct: A class is the definition (the blueprint) that describes what data and behaviour an object will have. An object is a concrete instance of that class — a real thing in memory with its own state.
Why A is incorrect: Both classes and objects are reference types (stored on the heap in .NET). The storage location depends on the type, not the class/object distinction.
Why C is incorrect: Both classes and objects have data and behaviour. A class defines both; an object has both.
Why D is incorrect: You use new to create objects from a class. The class itself is defined in source code.
Reinforcement: Class = blueprint, Object = building built from that blueprint.
2. Given the following class, what is the output?
public class Counter
{
public int Value = 0;
public static int Total = 0;
public Counter()
{
Total++;
}
public void Increment()
{
Value++;
Total++;
}
}
var a = new Counter();
var b = new Counter();
a.Increment();
b.Increment();
b.Increment();
Console.WriteLine($"{a.Value} {b.Value} {Counter.Total}"); Correct: A — 1 2 6
Why A is correct:
Total to 2.a.Increment() → a.Value = 1, Total = 3.b.Increment() → b.Value = 1, Total = 4.b.Increment() → b.Value = 2, Total = 5. Why B, C, D are incorrect: They miscount the static Total increments. The static field is shared and incremented in every constructor and every Increment() call.
Reinforcement: Static members belong to the class, not to individual objects. They are shared across all instances.
3. Which of the following correctly demonstrates encapsulation?
Correct: B
Why B is correct: Encapsulation means hiding internal details and controlling access through a public interface. Making fields private and exposing them via properties or methods with validation is the textbook way to achieve encapsulation.
Why A is incorrect: Making all fields public breaks encapsulation — external code can directly modify the object's state, bypassing any validation or logic.
Why C is incorrect: A class with only public fields and no methods is a data container, not an encapsulated object.
Why D is incorrect: A class with no fields and only static methods isn't an object-oriented design — it's more like a module or utility class.
Reinforcement: Encapsulation = hide internal state, expose controlled public API.
4. What does the following code print?
public class Person
{
public string Name { get; set; }
public Person(string name) => Name = name;
public void ChangeName(string newName) => Name = newName;
}
Person p1 = new Person("Alice");
Person p2 = p1;
p2.ChangeName("Bob");
Console.WriteLine(p1.Name); Correct: B — Bob
Why B is correct: Classes are reference types. p2 = p1 copies the reference, not the object. Both variables point to the same Person object. Changing the name through p2 modifies the same object that p1 references, so p1.Name is also "Bob".
Why A is incorrect: This would be true if Person were a struct (value type) where assignment copies the data. But class is a reference type.
Why C is incorrect: The object is never set to null.
Why D is incorrect: This is perfectly valid code and will not throw an exception.
Reinforcement: With reference types (classes), assignment copies the reference, not the object. Multiple variables can point to the same object.
5. Which of the following is a valid reason to use a private field with a public property instead of a public field?
Correct: B
Why B is correct: A property can contain logic in its getter and setter. This lets you validate values, raise events, log changes, or compute derived values — all without changing the public API.
Why A is incorrect: Properties have a slight overhead compared to field access (though it's usually negligible). They don't make code run faster.
Why C is incorrect: Properties have no effect on garbage collection. Objects are collected when unreferenced.
Why D is incorrect: Properties do not automatically provide thread safety. You still need locks or other synchronization if multiple threads access the same object.
Reinforcement: Use properties to encapsulate access to fields — they give you a control point for validation, logging, and future changes.
You now have a solid understanding of classes and objects — the foundational building blocks of object-oriented programming in C#!
dotnetmadeeasy.com — Learn C# and .NET, the right way.