Mastering C# .NET Software Development
C# Object-Oriented Programming
Blueprints of Code
Object-oriented programming, or OOP, helps us model real-world things in our code. At its heart are two key concepts: classes and objects.
Think of a class as a blueprint. If you're building a house, you start with a blueprint that defines its properties—like the number of bedrooms, bathrooms, and the color of the walls. The blueprint itself isn't a house; it's just the plan for one.
An object is the actual house built from that blueprint. You can build multiple houses from the same blueprint, and each one is a distinct object. They share the same structure but can have different details. One house might have blue walls, while another has green walls.
A class is the template. An object is a specific instance created from that template.
In C#, we define a class to specify the blueprint for our objects. Let's create a simple blueprint for a Car.
// This is our blueprint for a car
public class Car
{
// Properties every car will have
public string Color;
public string Model;
public int Year;
// A method, or an action the car can perform
public void StartEngine()
{
// In a real program, this would contain logic to start the engine.
Console.WriteLine("The " + Model + "'s engine is running.");
}
}
Now that we have the Car blueprint, we can create actual car objects from it. Each object is a new instance of the class.
// Create a new Car object from the Car class
Car myCar = new Car();
// Set the properties for this specific car
myCar.Color = "Red";
myCar.Model = "Mustang";
myCar.Year = 2023;
// Call its method
myCar.StartEngine(); // Output: The Mustang's engine is running.
// Create another, completely separate car object
Car friendsCar = new Car();
friendsCar.Color = "Blue";
friendsCar.Model = "Civic";
friendsCar.Year = 2022;
Keeping Data Safe
Encapsulation is a core OOP principle that's all about bundling data and the methods that work on that data within one unit—the class. More importantly, it's about controlling access to that data.
Imagine driving a real car. You use the steering wheel, pedals, and gear shift to control it. You don't interact directly with the engine's fuel injectors or the transmission's gears. The car's outer shell encapsulates the complex machinery, giving you a simple interface to use it safely.
In C#, we achieve this using access modifiers like public and private. Members marked public can be accessed from outside the class, like the steering wheel. Members marked private can only be accessed by code inside the class itself, like the internal engine parts.
Encapsulation protects an object's internal state from being changed in unexpected ways.
Let's update our Car class to properly encapsulate a speed variable. We don't want code outside our class to set the car's speed to a negative number. We can control this with a public method.
public class Car
{
private int _speed;
// Public method to safely set the speed
public void SetSpeed(int newSpeed)
{
if (newSpeed >= 0)
{
_speed = newSpeed;
}
else
{
// Handle the invalid input
Console.WriteLine("Speed cannot be negative.");
}
}
// Public method to get the current speed
public int GetSpeed()
{
return _speed;
}
}
With this setup, other parts of our program can't directly change _speed. They have to go through our SetSpeed method, which contains the logic to prevent invalid values. This makes our code more robust and easier to manage.
Building on What Exists
Inheritance allows a new class to absorb the properties and methods of an existing class. This creates an "is-a" relationship. For example, a sports car is a type of car. It has everything a regular car has, but it might add something new, like a turbocharger.
The class being inherited from is called the base or parent class. The class that inherits is called the derived or child class.
This principle is powerful because it promotes code reuse. Instead of copying and pasting code for common features, you can define them once in a base class and have multiple derived classes inherit them.
Let's define a general Vehicle class. Then, we can have more specific classes like Car and Motorcycle inherit from it.
// The base class
public class Vehicle
{
public string Manufacturer;
public void Start()
{
Console.WriteLine("Vehicle starting...");
}
}
// The derived class 'Car'
// The colon indicates that Car inherits from Vehicle
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
}
// Another derived class 'Motorcycle'
public class Motorcycle : Vehicle
{
public bool HasSidecar { get; set; }
}
Now, both Car and Motorcycle objects automatically have a Manufacturer property and a Start() method, because they inherited them from Vehicle. They also add their own unique properties.
Car myCar = new Car();
myCar.Manufacturer = "Ford"; // Inherited from Vehicle
myCar.NumberOfDoors = 4; // Specific to Car
myCar.Start(); // Inherited method, outputs "Vehicle starting..."
Motorcycle myBike = new Motorcycle();
myBike.Manufacturer = "Harley-Davidson"; // Inherited
myBike.Start(); // Inherited
One Name, Many Forms
Polymorphism, which means "many forms," allows us to treat objects of different classes in the same way. Specifically, it lets a derived class provide its own unique implementation of a method that it inherited from a base class.
For example, while all vehicles might have a Start() method, a car starts differently from a motorcycle. Polymorphism lets us call the same Start() method but get the correct, specific behavior for each object type.
This is done in C# using the virtual and override keywords. We mark a method in the base class as virtual to indicate that derived classes are allowed to replace it. The derived class then uses the override keyword to provide its new implementation.
public class Vehicle
{
// Mark the method as virtual
public virtual void DisplayInfo()
{
Console.WriteLine("This is a vehicle.");
}
}
public class Car : Vehicle
{
// Override the base class method
public override void DisplayInfo()
{
Console.WriteLine("This is a car.");
}
}
public class Motorcycle : Vehicle
{
// Override it again with different logic
public override void DisplayInfo()
{
Console.WriteLine("This is a motorcycle.");
}
}
The true power of polymorphism shines when you work with a collection of different objects that share the same base class. You can loop through them and call the same method on each one, and C# will automatically execute the correct version of that method based on the object's actual type.
// Create a list that can hold any kind of Vehicle
List<Vehicle> vehicles = new List<Vehicle>();
// Add different types of vehicles to the same list
vehicles.Add(new Vehicle());
vehicles.Add(new Car());
vehicles.Add(new Motorcycle());
// Loop through and call the same method on each object
foreach (Vehicle v in vehicles)
{
v.DisplayInfo();
}
// Output:
// This is a vehicle.
// This is a car.
// This is a motorcycle.
This ability to write flexible, generic code that works with many different object types is what makes polymorphism such a fundamental concept in object-oriented design.
Ready to check your understanding of these core principles?
In object-oriented programming, what is the relationship between a class and an object?
What is the primary goal of encapsulation?
Classes, encapsulation, inheritance, and polymorphism are the pillars of object-oriented programming. Mastering them is the key to writing clean, scalable, and maintainable C# applications.
