OOP Concepts C# : Mastering
Tags: csharp, coding, oop, programming
Published: 2024-02-28 18:27:43

**🚀 Object Oriented Programming** in C#(i.e great topic): OOP is a powerful paradigm that allows you to organize and structure your code in a more modular and reusable way.


**Encapsulation**- Encapsulation is about bundling the data (variables) and methods that operate on the data into a single unit or class. It also restricts direct access to some of an object's components, which can prevent the accidental modification of data. In C#, this is implemented using access modifiers like public, private, protected, and internal.

```csharp
public class Account
{
    private double balance;  // Private variable, encapsulated

    public double GetBalance()
    {
        return balance;
    }

    public void Deposit(double amount)
    {
        if (amount > 0)
        {
            balance += amount;
        }
    }
}
```


**Inheritance**- Inheritance allows you to create new classes based on existing classes. The new class(derived class) inherits the properties and methods of the existing class (base class). It helps in code reuse and creating a hierarchy of classes. In C#, this is denoted using the : symbol. 

```csharp
public class BaseAccount
{
    public double balance;
}

public class SavingsAccount : BaseAccount
{
    public double interestRate;

    public void ApplyInterest()
    {
        balance += balance * interestRate;
    }
}
```


**Polymorphism**- Polymorphism allows objects of different classes to be treated as objects of a common superclass. It enables us to use the same method name and signature to perform different actions based on the specific object being used. This can be achieved both statically at compile-time and dynamically at runtime. It's a powerful concept that adds flexibility and extensibility to our code.
 
**Method overloading** allows us to have multiple methods with the same name but different parameters within a class. 

**Method overriding** occurs when a subclass provides its own implementation of method i.e already defined in its superclass.

> In this example uses a base class Employee and two derived classes Manager and Intern to show how each can have a different implementation of the CalculateBonus method.

```csharp
using System;

// Base class
public class Employee
{
    public string Name { get; set; }
    public decimal Salary { get; set; }

    public Employee(string name, decimal salary)
    {
        Name = name;
        Salary = salary;
    }

    // Virtual method
    public virtual decimal CalculateBonus()
    {
        return Salary * 0.05m; // Standard 5% bonus for regular employees
    }

    public void Display()
    {
        Console.WriteLine($"{Name} earns a bonus of {CalculateBonus():C}");
    }
}

// Derived class for Managers
public class Manager : Employee
{
    public Manager(string name, decimal salary) : base(name, salary) { }

    public override decimal CalculateBonus()
    {
        return Salary * 0.10m; // 10% bonus for managers
    }
}

// Derived class for Interns
public class Intern : Employee
{
    public Intern(string name, decimal salary) : base(name, salary) { }

    public override decimal CalculateBonus()
    {
        return Salary * 0.01m; // 1% bonus for interns
    }
}

public class Program
{
    public static void Main()
    {
        Employee alice = new Employee("Alice", 50000);
        Manager bob = new Manager("Bob", 100000);
        Intern charlie = new Intern("Charlie", 20000);

        alice.Display();
        bob.Display();
        charlie.Display();
    }
}

```




**Abstraction**- Abstraction means hiding the complex reality while exposing only the necessary parts. It is generally implemented using abstract classes and interfaces.

```csharp
public abstract class Shape
{
    public abstract double Area();
}

public class Circle : Shape
{
    private double radius;

    public Circle(double radius)
    {
        this.radius = radius;
    }

    public override double Area()
    {
        return Math.PI * radius * radius;
    }
}
```

`There are additional concepts and features in object-oriented programming that are crucial for designing robust and maintainable systems. Here are some key additional concepts:`


**Classes** are the building blocks of object-oriented programming. The define the blueprint for creating objects. You can define properties (attributes) and methods (behaviors) within a class.

> Class- A class like Book serves as a blueprint for creating book objects. It includes characteristics (fields or properties) common to all books, such as title, author, and ISBN, as well as behaviors (methods) that a book might exhibit, like displaying its details.

```csharp
public class Book
{
    // Properties
    public string Title { get; set; }
    public string Author { get; set; }
    public string ISBN { get; set; }

    // Constructor
    public Book(string title, string author, string isbn)
    {
        Title = title;
        Author = author;
        ISBN = isbn;
    }

    // Method
    public void Display()
    {
        Console.WriteLine($"Title: {Title}, Author: {Author}, ISBN: {ISBN}");
    }
}
```

**Objects** are instances of a class. They represent specific instances of the class and can store data in their properties and perform actions using their methods.

> An object is an instance of a class. When you instantiate a Book, you are creating a specific book with actual values for title, author, and ISBN.

```csharp
public class Program
{
    public static void Main()
    {
        // Creating an object of Book
        Book myFavoriteBook = new Book("1984", "George Orwell", "978-0451524935");
        myFavoriteBook.Display();

        // Creating another book object
        Book anotherBook = new Book("Brave New World", "Aldous Huxley", "978-0060850524");
        anotherBook.Display();
    }
}
```

**Constructors**- Constructors are special methods in a class that are called when an object is instantiated. They are typically used to initialize an object's properties or to perform setup tasks. Constructors can be overloaded to provide multiple ways of initializing objects.

**- Default Constructor:** A default constructor is one that takes no arguments. It is automatically provided by the compiler if no constructors are explicitly defined.

```csharp
public class Employee
{
    public string Name;
    public int Age;

    // Default constructor
    public Employee()
    {
        Name = "Unknown";
        Age = 0;
    }
}
```

`Usage:`

```
 Employee employee1 = new Employee();
Console.WriteLine($"Name: {employee1.Name}, Age: {employee1.Age}");
```
**- Parameterized Constructor:** A parameterized constructor allows passing arguments to initialize your object's properties with specific values at the time of object creation.

```csharp
public class Employee
{
    public string Name;
    public int Age;

    // Parameterized constructor
    public Employee(string name, int age)
    {
        Name = name;
        Age = age;
    }
}
```
`Usage:`

```
Employee employee2 = new Employee("John Doe", 30);
Console.WriteLine($"Name: {employee2.Name}, Age: {employee2.Age}");

```

**- Static Constructor:** A static constructor is used to initialize static members of the class. It is called automatically before the first instance is created or any static members are referenced.

```csharp
public class DatabaseSettings
{
    public static string DatabaseName;
    public static int Timeout;

    // Static constructor
    static DatabaseSettings()
    {
        DatabaseName = "MyDatabase";
        Timeout = 30;
    }
}
```
`Usage`

```csharp
// Accessing a static property also triggers the static constructor
Console.WriteLine($"Database: {DatabaseSettings.DatabaseName}, Timeout: {DatabaseSettings.Timeout}");
```

Advanced Scenario: Combining Constructors

```csharp
public class Person
{
    public string FirstName;
    public string LastName;
    public int Age;

    // Default constructor
    public Person()
    {
        FirstName = "First";
        LastName = "Last";
    }

    // Parameterized constructor
    public Person(string firstName, string lastName, int age) : this() // Calls default constructor first
    {
        FirstName = firstName;
        LastName = lastName;
        Age = age;
    }
}

```

`Usage`

```csharp
Person person1 = new Person();
Person person2 = new Person("John", "Doe", 28);
Console.WriteLine($"Name: {person1.FirstName} {person1.LastName}");
Console.WriteLine($"Name: {person2.FirstName} {person2.LastName}, Age: {person2.Age}");

```

## Understanding the concept of Access Modifiers

Access modifiers in programming languages control the visibility and accessibility of classes, methods, and other members within a program. 
- They determine which parts of a program can access or modify a particular class, method, or variable. 
- Common access modifiers include public, private, protected, internal, protected internal, and private protected. 
- These modifiers help in enforcing encapsulation and ensuring that code is maintainable, secure, and easier to understand.


**Example Demonstrating Public, Private, and Protected Access Modifiers in C#**

```csharp
using System

public class ConnectionManager
{
 private bool isConnected;

 public ConnectionManager()
{
  isConnected = false;
}

public void SendConnectRequest()
{
  if(isConnected)
{
  Console.WriteLine("Connect request sent!");
}
  else
{
  Console.WriteLine("Connect request not sent. Not in a special state");
}
}
 protected void AcceptConnection()
{
  isConnected = true;
  Console.WriteLine("Connection accepted!");
}
}

public class Program
{
 public static void Main()
 {
  ConnectionManager manager = new ConnectionManager();
  manager.AcceptConnection();
  manager.SendConnectRequest();
 }
}
```

> In this example, a ConnectionManager class manages a connection state with private (isConnected), public (SendConnectRequest), and protected (AcceptConnection) members. It demonstrates how access modifiers control member visibility and accessibility.


**Public**: Public method are the interface through which other classes or code can interact with the object or class including outside classes and assemblies. They are often used for members that need to be widely accessible.

```csharp
using System;

public class Employee
{
    // Public properties
    public string Name { get; set; }
    public int Age { get; set; }
    public decimal Salary { get; set; }

    // Public constructor
    public Employee(string name, int age, decimal salary)
    {
        Name = name;
        Age = age;
        Salary = salary;
    }

    // Public method
    public void DisplayEmployeeDetails()
    {
        Console.WriteLine($"Name: {Name}, Age: {Age}, Salary: {Salary}");
    }
}

class Program
{
    static void Main()
    {
        // Creating an instance of Employee
        Employee employee = new Employee("John Doe", 30, 50000);

        // Accessing public properties
        Console.WriteLine($"Name: {employee.Name}");
        Console.WriteLine($"Age: {employee.Age}");
        Console.WriteLine($"Salary: {employee.Salary}");

        // Calling public method
        employee.DisplayEmployeeDetails();
    }
}

```

> In this example, the Employee class has public properties (Name, Age, Salary), a public constructor, and a public method (DisplayEmployeeDetails). These members can be accessed from outside the class and are part of the class's public interface.



**Private**: Private methods are often use to encapsulate internal implementation details that are not relevant or necessary for other part of the code to know about.

```csharp
using System;

public class Employee
{
    // Private fields
    private string name;
    private int age;
    private decimal salary;

    // Public constructor
    public Employee(string name, int age, decimal salary)
    {
        // Using private fields directly within the class
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    // Private method
    private void DisplaySalary()
    {
        Console.WriteLine($"Salary: {salary}");
    }

    // Public method to display employee details
    public void DisplayEmployeeDetails()
    {
        Console.WriteLine($"Name: {name}, Age: {age}");
        // Calling a private method within the class
        DisplaySalary();
    }
}

class Program
{
    static void Main()
    {
        // Creating an instance of Employee
        Employee employee = new Employee("John Doe", 30, 50000);

        // Accessing public method to display employee details
        employee.DisplayEmployeeDetails();

        // Private fields cannot be accessed outside the class
        // Console.WriteLine(employee.name); // This would cause a compile-time error
    }
}

```

> In this example, the Employee class has private fields (name, age, salary), a private method (DisplaySalary), and a public method (DisplayEmployeeDetails). Private members can only be accessed within the class itself and are not visible from outside the class.



**Protected**: Protected method provide a level of abstraction that allows for customization and extension of the base class. Protected members are accessible within the same class or by derived classes (subclasses). They cannot be accessed from outside the defining class or its subclasses.


```csharp
using System;

public class Employee
{
    // Protected fields
    protected string name;
    protected int age;
    protected decimal salary;

    // Public constructor
    public Employee(string name, int age, decimal salary)
    {
        // Using protected fields directly within the class
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    // Protected method
    protected void DisplaySalary()
    {
        Console.WriteLine($"Salary: {salary}");
    }

    // Public method to display employee details
    public void DisplayEmployeeDetails()
    {
        Console.WriteLine($"Name: {name}, Age: {age}");
        // Calling a protected method within the class
        DisplaySalary();
    }
}

// Derived class
public class Manager : Employee
{
    public Manager(string name, int age, decimal salary) : base(name, age, salary)
    {
    }

    // Public method to display manager details
    public void DisplayManagerDetails()
    {
        // Protected members can be accessed in derived classes
        Console.WriteLine($"Name: {name}, Age: {age}");
        // Calling a protected method from the base class
        DisplaySalary();
    }
}

class Program
{
    static void Main()
    {
        // Creating an instance of Manager
        Manager manager = new Manager("Jane Smith", 35, 60000);

        // Accessing public methods to display employee and manager details
        manager.DisplayEmployeeDetails();
        manager.DisplayManagerDetails();

        // Protected fields cannot be accessed outside the class hierarchy
        // Console.WriteLine(manager.name); // This would cause a compile-time error
    }
}

```

> In this example, the Employee class has protected fields (name, age, salary) and a protected method (DisplaySalary). Protected members can be accessed within the class itself, in derived classes, and from instances of the derived classes. They are not visible outside the class hierarchy.



**Internal**: Internal members are accessible within the same assembly (or module). They cannot be accessed from outside assemblies.

```csharp
using System;

public class Employee
{
    // Internal fields
    internal string name;
    internal int age;
    internal decimal salary;

    // Public constructor
    public Employee(string name, int age, decimal salary)
    {
        // Using internal fields directly within the class
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    // Internal method
    internal void DisplaySalary()
    {
        Console.WriteLine($"Salary: {salary}");
    }

    // Public method to display employee details
    public void DisplayEmployeeDetails()
    {
        Console.WriteLine($"Name: {name}, Age: {age}");
        // Calling an internal method within the class
        DisplaySalary();
    }
}

// Another class in the same assembly
public class AnotherClass
{
    public void AccessInternalMember()
    {
        Employee employee = new Employee("John Doe", 30, 50000);
        // Internal members can be accessed in the same assembly
        Console.WriteLine($"Employee Name: {employee.name}");
        employee.DisplaySalary();
    }
}

class Program
{
    static void Main()
    {
        // Creating an instance of Employee
        Employee employee = new Employee("John Doe", 30, 50000);

        // Accessing public method to display employee details
        employee.DisplayEmployeeDetails();

        // Internal fields and methods can be accessed within the same assembly
        // Console.WriteLine(employee.name); // This would cause a compile-time error outside the assembly
    }
}

```

> In this example, the Employee class has internal fields (name, age, salary) and an internal method (DisplaySalary). Internal members can be accessed within the same assembly but are not visible outside the assembly.


**Protected Internal**: Protected Internal members are accessible within the same assembly or by derived classes in other assemblies.

```csharp
using System;

public class Employee
{
    // Protected internal fields
    protected internal string name;
    protected internal int age;
    protected internal decimal salary;

    // Public constructor
    public Employee(string name, int age, decimal salary)
    {
        // Using protected internal fields directly within the class
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    // Protected internal method
    protected internal void DisplaySalary()
    {
        Console.WriteLine($"Salary: {salary}");
    }

    // Public method to display employee details
    public void DisplayEmployeeDetails()
    {
        Console.WriteLine($"Name: {name}, Age: {age}");
        // Calling a protected internal method within the class
        DisplaySalary();
    }
}

// Another class in the same assembly
public class AnotherClass
{
    public void AccessProtectedInternalMember()
    {
        Employee employee = new Employee("John Doe", 30, 50000);
        // Protected internal members can be accessed in the same assembly
        Console.WriteLine($"Employee Name: {employee.name}");
        employee.DisplaySalary();
    }
}

// Derived class in the same assembly
public class Manager : Employee
{
    public Manager(string name, int age, decimal salary) : base(name, age, salary)
    {
    }

    // Public method to display manager details
    public void DisplayManagerDetails()
    {
        // Protected internal members can be accessed in derived classes
        Console.WriteLine($"Name: {name}, Age: {age}");
        // Calling a protected internal method from the base class
        DisplaySalary();
    }
}

class Program
{
    static void Main()
    {
        // Creating an instance of Manager
        Manager manager = new Manager("Jane Smith", 35, 60000);

        // Accessing public methods to display employee and manager details
        manager.DisplayEmployeeDetails();
        manager.DisplayManagerDetails();

        // Protected internal fields and methods can be accessed within the same assembly
        // Console.WriteLine(manager.name); // This would cause a compile-time error outside the assembly
    }
}

```

> In this example, the Employee class has protected internal fields (name, age, salary) and a protected internal method (DisplaySalary). Protected internal members can be accessed within the same assembly or in derived classes, but they are not visible outside the assembly.



**Private Protected**: Private Protected members are accessible only within the same assembly and by derived classes.
- It allows a member to be accessible within its containing type and from types derived from the containing type, but only within its containing assembly. 
- This access level is a combination of the private and protected internal access modifiers.

```csharp
using System;

public class Employee
{
    private protected string name;
    private protected int age;

    public Employee(string name, int age)
    {
        this.name = name;
        this.age = age;
    }

    private protected void DisplayDetails()
    {
        Console.WriteLine($"Name: {name}, Age: {age}");
    }
}

public class Manager : Employee
{
    public Manager(string name, int age) : base(name, age)
    {
    }

    public void DisplayEmployeeDetails()
    {
        // Can access 'name' and 'age' because they are 'private protected'
        DisplayDetails();
    }
}

class Program
{
    static void Main()
    {
        Manager manager = new Manager("John Doe", 40);
        manager.DisplayEmployeeDetails(); // Outputs: Name: John Doe, Age: 40
    }
}

```

> In this example, the Employee class has name and age fields with a private protected access modifier. The DisplayDetails method is also private protected, which allows it to be accessed by the Manager class, which derives from Employee. The DisplayEmployeeDetails method in the Manager class calls DisplayDetails to show the employee's details.


Note: Access modifiers in C# control the visibility and accessibility of classes, methods, and other members within a program, ensuring encapsulation and defining the scope of access. They help manage code complexity, maintainability, and security.