Classes and Objects
By the end of this lesson
Define a class that keeps related data together, and explain how reference types differ from value types.
Earlier you held employee names in one array and roles in another, and nothing stopped them drifting out of alignment. A class fixes that by keeping related values together in one thing.
A class is a description. An object is one actual item made from that description.
public class Employee
{
public string Name { get; set; } = "";
public string Role { get; set; } = "";
public decimal AnnualSalary { get; set; }
}
Employee asha = new Employee
{
Name = "Asha",
Role = "Engineer",
AnnualSalary = 1_200_000m
};
Console.WriteLine($"{asha.Name} works as a {asha.Role}.");- The class names three pieces of data that belong together. Each is a property.
- new Employee { ... } creates one object and sets its values.
- Name and Role cannot now be separated, which removes the whole class of bug the parallel arrays invited.
List<Employee> employees = new List<Employee>
{
new Employee { Name = "Asha", Role = "Engineer", AnnualSalary = 1_200_000m },
new Employee { Name = "Ravi", Role = "Designer", AnnualSalary = 950_000m },
new Employee { Name = "Meera", Role = "Analyst", AnnualSalary = 1_050_000m }
};
foreach (Employee employee in employees)
{
Console.WriteLine($"{employee.Name,-8} {employee.Role,-10} {employee.AnnualSalary:N0}");
}- One list, each item carrying all of its own data. Sorting or filtering it cannot desynchronise anything.
- ,-8 pads to eight characters for aligned output, and :N0 formats the number with thousands separators.
The part that surprises people: reference types
A class is a reference type. A variable of that type does not hold the object itself — it holds a reference to where the object is. Two variables can refer to the same object, and changing it through one is visible through the other.
This catches nearly everyone once, and understanding it now saves a genuinely confusing afternoon later.
Employee first = new Employee { Name = "Asha", Role = "Engineer" };
Employee second = first; // not a copy — the same object
second.Role = "Lead Engineer";
Console.WriteLine(first.Role); // Lead Engineer
Console.WriteLine(second.Role); // Lead Engineer- second = first copies the reference, not the object. Both now point at one Employee.
- Changing Role through second is therefore visible through first, because there is only one object.
Contrast with a value type, where assignment does copy:
| Value type (int, decimal, bool, struct) | Reference type (class, string[], List) | |
|---|---|---|
| What the variable holds | The value itself | A reference to the object |
| Assignment copies | The value | The reference |
| Change via a second variable | Not visible in the first | Visible in the first |
| Can it be null? | Only if declared nullable | Yes |
int firstCount = 5;
int secondCount = firstCount; // a genuine copy
secondCount = 99;
Console.WriteLine(firstCount); // 5 — unaffected
Console.WriteLine(secondCount); // 99Summary
- A class describes a type; an object is one instance of it
- Grouping related data in a class removes the risk of parallel collections drifting apart
- Classes are reference types: assignment copies the reference, not the object
- Value types copy on assignment; reference types do not
- To copy an object you must create a new one deliberately
Practice
Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.
Try it yourself
Try it yourself
Create a Product class with a name, a price and a quantity in stock. Build a list of four products and print the total value of the inventory.
Show solution
Each product carries its own three values, so the calculation reads naturally and nothing can drift out of alignment.
public class Product
{
public string Name { get; set; } = "";
public decimal Price { get; set; }
public int QuantityInStock { get; set; }
}
List<Product> products = new List<Product>
{
new Product { Name = "Notebook", Price = 120m, QuantityInStock = 40 },
new Product { Name = "Pen", Price = 25m, QuantityInStock = 200 },
new Product { Name = "Desk lamp", Price = 1450m, QuantityInStock = 12 },
new Product { Name = "Cable tidy", Price = 300m, QuantityInStock = 55 }
};
decimal inventoryValue = 0m;
foreach (Product product in products)
{
inventoryValue += product.Price * product.QuantityInStock;
}
Console.WriteLine($"Inventory value: {inventoryValue:N2}");Challenge
Challenge
Write a method that takes a Product and applies a 10% discount to its price. Call it, then print the original variable's price.
Explain why the original changed, and then write a version that returns a discounted copy instead of modifying the original.
Show solution
The original changed because the method received a reference to the same object, not a copy. Modifying it inside the method modifies the one object that both the caller and the method can see.
Whether that is desirable depends on intent. A method named ApplyDiscount modifying its argument is arguably reasonable; one named CalculateDiscountedPrice should not. Being deliberate about which you are writing prevents a common category of surprise.
// Modifies the object the caller holds
void ApplyDiscount(Product product)
{
product.Price *= 0.9m;
}
// Leaves the original untouched and returns a new object
Product WithDiscount(Product product)
{
return new Product
{
Name = product.Name,
Price = product.Price * 0.9m,
QuantityInStock = product.QuantityInStock
};
}Knowledge check
Nothing is recorded and there is no score. The explanation appears either way.
Saved in this browser only.