🏗️Object-Oriented Programming in C#

Dive into OOP principles like encapsulation, inheritance, polymorphism, and interface-driven design to build modular and maintainable C# applications.

🏛️ Classes, Objects, and Constructors

In C#, classes are blueprints for creating objects. They encapsulate data (fields) and behavior (methods). Think of a class as a template that defines what properties and actions an object will have.

  • Classes define the structure of objects.
  • Objects are instances of classes.
  • Classes can contain fields, properties, methods, and constructors.

💡 Key Concepts About Classes

  • Classes are created using the class keyword.
  • Every C# program contains at least one class, typically a Program class.
  • You can create multiple objects from a single class.

Creating Objects

To create an object, you use the instantiation process. This involves using the new keyword followed by the class name and parentheses.

Person person = new Person();

💡 Understanding Constructors

A constructor is a special method that initializes an object when it's created. It has the same name as the class and no return type.

  • Constructors are used to set initial values for fields.
  • You can have multiple constructors in a class (constructor overloading).
  • If you don't define a constructor, C# provides a default parameterless constructor.

Parameterized Constructors

A parameterized constructor allows you to pass values when creating an object. This is useful for setting initial state.

public Person(string name, int age)
{
    this.Name = name;
    this.Age = age;
}

💡 Real-World Application of Classes and Objects

Imagine building an application for a library. You might create a Book class with properties like Title, Author, ISBN, and methods like Checkout() and Return(). Each book in the library is an instance of this class.

Best Practices for Constructors

  • Use constructors to initialize required fields.
  • Follow the DRY principle - don't repeat initialization code.
  • Consider using constructor overloading for flexibility.

Common Mistakes to Avoid

  • Don't create unnecessary constructors.
  • Avoid excessive parameters in a single constructor (use overloading instead).
  • Never forget to initialize essential fields.

👨‍👩‍👧‍👦 Inheritance and Polymorphism

Inheritance is a fundamental concept in object-oriented programming that allows you to create a new class by extending an existing one. This promotes code reusability and creates a natural hierarchy between classes.

  • Inherits properties and methods from the base class
  • Can add new functionality or override existing behavior
  • Creates an 'is-a' relationship between classes

💡 Key Points About Inheritance

  • Use the :class keyword to derive a class from another
  • Derived classes can access base class members unless they are marked as private or protected
  • C# supports single inheritance only (cannot inherit from multiple classes)

💡 Polymorphism in C#

Polymorphism means 'many forms' - the ability of an object to take on many shapes. In C#, this is achieved through method overriding and abstract classes.

// Base class
public class Animal {
    public virtual void Speak() {
        Console.WriteLine("Some noise");
    }
}

// Derived class
public class Dog : Animal {
    public override void Speak() {
        Console.WriteLine("Woof!");
    }
}

Method Overriding

  • Use the override keyword to specify that a method is overriding one from the base class
  • The method must have the same signature as the virtual method in the base class
  • Provides a way to implement specific behavior in derived classes

Abstract Classes and Methods

An abstract class cannot be instantiated and is meant to be inherited. Abstract methods have no implementation in the base class and must be overridden by derived classes.

public abstract class Shape {
    public abstract void Draw();
}

public class Circle : Shape {
    public override void Draw() {
        Console.WriteLine("Drawing a circle");
    }
}

Common Mistakes to Avoid

  • Don't forget the base keyword when calling base class constructors in derived classes
  • Avoid deep inheritance chains (favor composition over inheritance)
  • Don't override methods just for code reuse - use virtual/abstract appropriately

💡 Best Practices

  • Use inheritance to represent 'is-a' relationships, composition for 'has-a'
  • Keep base classes as simple and general-purpose as possible
  • Favor interface-based programming for maximum flexibility

🔐 Encapsulation and Access Modifiers

Welcome to the world of encapsulation in C#! This powerful concept allows you to create classes that are both secure and easy to use. By controlling access to class members, you can protect internal logic while exposing meaningful interfaces.

💡 Key Concepts

  • Encapsulation is about hiding internal implementation details
  • Access modifiers control visibility and accessibility
  • Public members are accessible from anywhere
  • Private members are only accessible within the same class
  • Protected members can be accessed by derived classes

💡 Understanding Access Modifiers

C# provides several access modifiers to control how class members can be accessed:

// Public member accessible everywhere
public int MaxSpeed = 200;

// Private member only within the class
private string secretKey = "12345";

💡 Public Access Modifier

The public access modifier allows unrestricted access to members from any part of the program. Use this for members that need to be widely available.

public class Car {
    public void StartEngine() {
        // Implementation
    }
}

💡 Private Access Modifier

The private access modifier restricts access to the same class only. Use this for implementation details that shouldn't be exposed.

public class Car {
    private string fuelType;

    public void SetFuel(string type) {
        fuelType = type;
    }
}

💡 Protected Access Modifier

The protected access modifier allows access within the same class and its derived classes. Use this for inheritance scenarios.

public class Vehicle {
    protected int SpeedLimit;
}

public class Car : Vehicle {
    public void SetSpeed(int speed) {
        SpeedLimit = speed;
    }
}

💡 Internal Access Modifier

The internal access modifier restricts access to the same assembly only. Use this for members that need to be accessible throughout your application but not exposed externally.

internal class DatabaseConfig {
    public string ConnectionString;
}

💡 Protected Internal Access Modifier

The protected internal access modifier combines protected and internal. Members can be accessed within the same assembly or by derived classes in any assembly.

public class BaseClass {
    protected internal virtual void Execute() {
        // Implementation
    }
}

💡 Best Practices for Encapsulation

  • Use encapsulation to hide internal implementation details
  • Prefer private or protected for non-public members
  • Use public only for necessary interface members
  • Avoid using internal for security-critical code
  • Document access modifiers and their reasoning

🪄 Abstract Classes and Interfaces

Welcome to Abstract Classes and Interfaces in C#! In this chapter, you'll learn how to create contracts for behavior using abstract classes and interfaces. These concepts are fundamental to writing scalable, maintainable code that adheres to the principles of object-oriented programming.

💡 Abstract Classes vs Interfaces: What's the Difference?

Both abstract classes and interfaces define contracts for behavior, but they serve different purposes. Let's explore their key differences:

  • Abstract classes are classes that cannot be instantiated, while interfaces are purely abstract contracts.
  • Abstract classes can have implementation details, while interfaces can only declare methods and properties.
  • Abstract classes use the abstract keyword to declare methods, while interfaces use method signatures without implementation.
// Abstract class example
public abstract class Animal {
    public abstract void MakeSound();
}

// Interface example
public interface IMovable {
    void Move();
}

When to Use Abstract Classes

Use abstract classes when you need to provide a °base implementation that can be shared by multiple derived classes§. This is particularly useful in scenarios where: - You want to share common code between related classes - You need to enforce a contract while providing default behavior

// Abstract class with base implementation
public abstract class Shape {
    public abstract double CalculateArea();

    public virtual void Print() {
        Console.WriteLine("This is a shape.");
    }
}

// Derived class
public class Circle : Shape {
    public override double CalculateArea() {
        return Math.PI * r * r;
    }
}

When to Use Interfaces

Use interfaces when you need to define a °contract for behavior without any implementation details§. This is ideal in scenarios where: - You want to decouple classes from their implementations - You need multiple inheritance (since C# only supports single inheritance) - You're designing APIs or contracts that can be implemented by unrelated classes

// Interface example
public interface IPayable {
    void Pay();
}

// Implementing the interface
public class Employee : IPayable {
    public void Pay() {
        Console.WriteLine("Paying employee salary.");
    }
}

💡 Key Differences Between Abstract Classes and Interfaces

  • Abstract classes can have abstract methods, while interfaces only declare method signatures.
  • Abstract classes can have implemented methods, while interfaces cannot.
  • Abstract classes use inheritance, while interfaces are implemented via composition.
  • A class can implement multiple interfaces, but can only inherit from one abstract class.

💡 Interface-Driven Design: Best Practices

Interface-driven design (IDD) is a powerful approach that emphasizes defining contracts before implementation. Here's how to apply it effectively:

  • Define interfaces first: Start by identifying the behaviors your classes need to expose.
  • Use interfaces for contracts: Ensure classes implement these contracts without exposing internal details.
  • Decouple components: Interfaces allow loose coupling between different parts of your system.
  • Promote polymorphism: Use interfaces to enable method overriding and flexible behavior.
// Interface-driven design example
public interface IUserService {
    Task<User> GetUserById(int id);
}

public class UserService : IUserService {
    public async Task<User> GetUserById(int id) {
        // Implementation details here
    }
}

Common Pitfalls to Avoid

  • Don't create empty interfaces: Every interface should define meaningful behavior.
  • Avoid overusing inheritance: Prefer composition over inheritance when possible.
  • Don't mix concerns in abstract classes: Keep base implementations focused and single-purpose.
  • Don't use interfaces for implementation details: Interfaces should define 'what' not 'how'.

Quiz

Question 1 of 22

Which of the following is true about C# classes?

  • Classes cannot contain other classes
  • A class can have multiple constructors
  • All classes must explicitly declare a constructor
  • Objects are used to create classes