LEARNING OUTCOME 2
APPLY OBJECT-ORIENTED PROGRAMMING PRINCIPLES TO SOLVE PROGRAMMING PROBLEMS AND WRITE EFFICIENT CODE
CLASS VS. OBJECT
In C#, classes and objects are fundamental concepts for building applications. Here's a breakdown to clarify the distinction:
CLASS:
- A class is like a blueprint or template that defines the properties (data) and methods (functions) that objects of that class will have. It acts as a reusable specification for creating objects.
- Think of a class like a cookie cutter for making cookies. The cookie cutter defines the general shape and features of the cookie, but it's not the actual cookie itself.
OBJECT:
- An object is an instance of a class. It's a concrete entity that holds data specific to that object and can execute the methods defined in the class. You can create multiple objects from a single class, just like you can make many cookies from the same cookie cutter.
- Each object has its own copy of the properties defined in the class, with values specific to that object.
Here's a table summarizing the key differences:
Feature | Class | Object |
---|---|---|
Represents | Blueprint or template | Concrete instance |
Reusability | Reusable | Not reusable (multiple objects can be created from a class) |
Properties | Defines properties (data) | Holds values for those properties |
Methods | Defines methods (functions) | Can execute those methods |
Example:
public class Car // Class definition (blueprint)
{
public string Model { get; set; } // Property (data)
public int Year { get; set; } // Property (data)
public void StartEngine() // Method (function)
{
Console.WriteLine("Engine started!");
}
}
// ... in another part of the code (e.g., Main method) ...
Car myCar = new Car(); // Creating an object of the Car class
myCar.Model = "Tesla Model S"; // Assigning values to object properties
myCar.Year = 2024;
myCar.StartEngine(); // Calling a method on the object
In this example, the Car
class defines the blueprint for car objects. We can then create objects (instances) like myCar
from this class. Each Car
object will have its own Model
and Year
properties, along with the ability to call the StartEngine()
method.
CONSTRUCTORS AND DESTRUCTORS IN C#
CONSTRUCTOR:
- A constructor is a special method in a class that is called automatically when you create an object of that class (using the `new` keyword). Its primary purpose is to initialize the object's state (its fields and properties) with appropriate starting values.
- Constructors have the same name as the class and do not have a return type (not even `void`).
- Think of a constructor as the setup instructions executed when a new object comes into existence.
Example:
public class Car
{
public string Model { get; set; }
public int Year { get; set; }
// Constructor with parameters
public Car(string model, int year)
{
Model = model; // Initialize Model property
Year = year; // Initialize Year property
Console.WriteLine($"A {Year} {Model} was created!");
}
}
// ... later in the code ...
Car myCar = new Car("Ford Mustang", 2020); // Constructor is called here
In this example, the Car
class has a constructor that takes two arguments. When we create myCar
using new Car(...)
, this constructor is executed, setting the Model
and Year
and printing a message.
DESTRUCTOR:
- A destructor (using the tilde `~` followed by the class name) is a special method used to perform cleanup operations before an object is finally removed from memory by the Garbage Collector (GC).
- Its main use case is to release **unmanaged resources** (like operating system file handles, database connections, network sockets, or memory allocated outside the .NET runtime) that the object might hold.
- Destructors cannot be called explicitly, have no parameters, and no access modifiers. Their execution timing is non-deterministic, controlled by the GC.
Important Note: For cleanup of *managed* resources or for more deterministic cleanup, implementing the IDisposable
interface and using the using
statement is the preferred and recommended pattern in C# over relying solely on destructors.
ACCESS SPECIFIERS
In C#, access specifiers (or access modifiers) are keywords used to define the visibility or accessibility level of types (like classes, structs, interfaces) and their members (fields, properties, methods). They control which parts of your code can access these elements.
-
public
:The type or member can be accessed by any code in the same assembly or another assembly that references it. This is the most permissive level.
Example:
-
private
:The type or member can only be accessed by code within the same class or struct that declares it. This is the most restrictive level and the default for members if no modifier is specified.
Example:
-
protected
:The type or member can be accessed only by code in the same class, or in a class that is derived (inherits) from that class.
Example:
-
internal
:The type or member can be accessed by any code within the same assembly (the project/DLL where they are defined). This is the default accessibility for top-level types (like classes) if none is specified.
HelperUtility
can be used by any other class withinMyLibrary.dll
, but not by code in a different project referencingMyLibrary.dll
. -
protected internal
:The type or member can be accessed by any code in the assembly in which it's declared, OR from within a derived class in another assembly. It's a union of
protected
andinternal
access. -
private protected
:The type or member can be accessed only within its declaring assembly, by code in the same class or in a type that is derived from that class. This is the most restrictive combination.
ENCAPSULATION, INHERITANCE, AND POLYMORPHISM
ENCAPSULATION:
- Concept: Bundling data (attributes/fields/properties) and the methods (functions/behavior) that operate on that data together within a single unit, the class. A key part is often **data hiding**, restricting direct access to some of the object's components.
- Analogy: Think of a car. You interact with it through pedals, steering wheel, and ignition (public methods/interface), but the complex engine workings (private data and methods) are hidden away and managed internally.
- Implementation in C#: Using access modifiers like `private` for data fields and providing controlled access through `public` properties (getters/setters) or methods.
BENEFITS OF ENCAPSULATION:
- Control & Integrity: Prevents outside code from putting the object into an invalid or inconsistent state. The class controls how its data is modified.
- Simplicity: Hides internal complexity from the user of the class. They only need to know the public interface.
- Maintainability & Flexibility: Allows the internal implementation of a class to be changed without affecting the code that uses it, as long as the public interface remains stable.
INHERITANCE:
- Concept: A mechanism where one class (the derived or child class) acquires the properties and methods of another class (the base or parent class). It represents an "is-a" relationship (e.g., a `Dog` is an `Animal`).
- Analogy: A `Square` "is-a" type of `Shape`. It inherits general shape properties (like position) but adds its own specific properties (side length) or behavior.
- Implementation in C#: Using the colon (`:`) after the derived class name, followed by the base class name (e.g., `public class Dog : Animal`).
TYPES OF INHERITANCE (in C# context):
- Single Class Inheritance: A class can directly inherit from only one base class.
- Multiple Interface Implementation: A class can implement multiple interfaces, inheriting their contracts (method signatures, properties) but not their implementation (unless default interface methods are used).
- Multilevel Inheritance: Class C inherits from B, which inherits from A.
- Hierarchical Inheritance: Multiple classes (B, C) inherit from a single base class (A).
BENEFITS OF INHERITANCE:
- Code Reusability: Avoids duplicating code by defining common attributes and behaviors in a base class.
- Extensibility: Easily create new classes that build upon existing ones.
- Polymorphism Foundation: Inheritance is essential for achieving runtime polymorphism through method overriding.
POLYMORPHISM:
- Concept: Literally means "many forms." In OOP, it's the ability of objects of different classes (related by inheritance) to respond to the same method call in their own unique way. It allows you to treat objects of derived classes as objects of their base class type.
- Analogy: Different animals (`Dog`, `Cat`) might all respond to a `MakeSound()` command, but each produces its specific sound ("Woof!", "Meow!"). You can tell any `Animal` to `MakeSound()`, and it will do so appropriately based on what kind of animal it actually is.
TYPES OF POLYMORPHISM IN C#:
-
Static (Compile-Time) Polymorphism: Resolved by the compiler before the program runs.
- Method Overloading: Same method name, different parameter lists within the same class.
- Operator Overloading: Custom behavior for operators (+, -, etc.) for a class.
-
Dynamic (Runtime) Polymorphism: Resolved when the program is running, based on the actual object type.
- Method Overriding: A derived class provides a specific implementation for a `virtual` or `abstract` method inherited from its base class, using the `override` keyword.
BENEFITS OF POLYMORPHISM:
- Flexibility & Extensibility: Allows you to add new derived classes without changing the code that uses the base class references.
- Simpler Code: You can write code that operates on base class types, and it will automatically work correctly with any derived type objects.
Example Combining Principles:
using System;
using System.Collections.Generic;
// Base class (Encapsulation: private fields, public properties/methods)
public abstract class Shape
{
// Encapsulated data (can only be accessed via property)
private string _color;
public string Color
{
get { return _color; }
protected set { _color = value; } // Protected set allows derived classes to set color
}
// Constructor
public Shape(string color)
{
this.Color = color;
}
// Abstract method - must be implemented by derived classes (Polymorphism)
public abstract double GetArea();
// Virtual method - can be optionally overridden (Polymorphism)
public virtual void DisplayInfo()
{
Console.WriteLine($"Color: {Color}");
}
}
// Derived class (Inheritance)
public class Circle : Shape
{
public double Radius { get; private set; } // Encapsulated property
// Constructor calls base constructor
public Circle(string color, double radius) : base(color)
{
Radius = radius;
}
// Override abstract method (Polymorphism)
public override double GetArea()
{
return Math.PI * Radius * Radius;
}
// Override virtual method (Polymorphism)
public override void DisplayInfo()
{
base.DisplayInfo(); // Call base method
Console.WriteLine($"Type: Circle, Radius: {Radius}");
}
}
// Another derived class (Inheritance)
public class Rectangle : Shape
{
public double Width { get; private set; }
public double Height { get; private set; }
public Rectangle(string color, double width, double height) : base(color)
{
Width = width;
Height = height;
}
public override double GetArea()
{
return Width * Height;
}
public override void DisplayInfo()
{
base.DisplayInfo();
Console.WriteLine($"Type: Rectangle, Width: {Width}, Height: {Height}");
}
}
public class Program
{
static void Main(string[] args)
{
List<Shape> shapes = new List<Shape>();
shapes.Add(new Circle("Red", 5.0));
shapes.Add(new Rectangle("Blue", 4.0, 6.0));
shapes.Add(new Circle("Green", 2.5));
Console.WriteLine("--- Shape Details ---");
foreach (Shape shape in shapes)
{
shape.DisplayInfo(); // Polymorphic call to DisplayInfo
Console.WriteLine($"Area: {shape.GetArea():F2}"); // Polymorphic call to GetArea
Console.WriteLine("--------------------");
}
}
}
Explanation:
- Encapsulation: The `Shape` class hides its `_color` field, providing controlled access via the `Color` property. `Circle` and `Rectangle` also encapsulate their specific dimensions (`Radius`, `Width`, `Height`) using auto-properties with `private set` (allowing them to be set only within the class, typically in the constructor).
- Inheritance: `Circle` and `Rectangle` inherit from the `Shape` base class using the `: Shape` syntax. They automatically gain the `Color` property and the requirement to implement `GetArea()`. They also inherit the `DisplayInfo()` method.
- Polymorphism:
- `GetArea()` is `abstract` in `Shape`, forcing derived classes to provide their own implementation (method overriding).
- `DisplayInfo()` is `virtual` in `Shape`, allowing derived classes to optionally `override` it to add more specific details, while still being able to call the base implementation using `base.DisplayInfo()`.
- In `Main`, a `List
` holds different actual types (`Circle`, `Rectangle`). When iterating through the list, calling `shape.DisplayInfo()` and `shape.GetArea()` invokes the correct overridden method for each object at runtime. This is dynamic/runtime polymorphism.
This enhanced example showcases how abstract methods enforce polymorphism and how virtual methods allow optional extension while still leveraging base class functionality, alongside encapsulation and inheritance.