Md Mominul Islam | Software and Data Enginnering | SQL Server, .NET, Power BI, Azure Blog

while(!(succeed=try()));

LinkedIn Portfolio Banner

Latest

Home Top Ad

Responsive Ads Here

Sunday, August 17, 2025

Complete C# Course: From Beginner to Advanced – Module 4: Object-Oriented Programming (OOP)

 

Introduction

Welcome to Module 4 of our Complete C# Course: From Beginner to Advanced! After mastering methods and exception handling in Module 3, it’s time to dive into Object-Oriented Programming (OOP), the backbone of structured C# development. In this module, we’ll cover classes and objects, constructors and destructors, properties and fields, encapsulation, inheritance, polymorphism, abstraction, and static vs. instance members. We’ll apply these concepts in a practical library management system to manage books and users, inspired by real-world scenarios like library or bookstore apps. With detailed examples, best practices, and pros/cons, you’ll learn to build modular, scalable C# programs. Let’s get started!


1. Classes and Objects

Classes define blueprints for objects, which are instances of classes containing data and behavior.

Example: Book Class

using System;

namespace LibrarySystem
{
    class Book
    {
        public string Title;
        public string Author;

        public void DisplayInfo()
        {
            Console.WriteLine($"Book: {Title} by {Author}");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Book book = new Book(); // Object creation
            book.Title = "C# Unleashed";
            book.Author = "John Doe";
            book.DisplayInfo(); // Book: C# Unleashed by John Doe
        }
    }
}
  • Real-World Use: Modeling entities like products, users, or orders in business applications.

  • Pros:

    • Encapsulates data and behavior together.

    • Promotes reusability and modularity.

  • Cons:

    • Overuse of classes can overcomplicate simple logic.

    • Requires understanding of object lifecycle.

  • Best Practices:

    • Use meaningful class names (e.g., Book vs. Class1).

    • Keep classes focused on a single responsibility.

  • Alternatives:

    • Structs for lightweight data structures.

    • Records (C# 9+) for immutable data.


2. Constructors and Destructors

Constructors initialize objects, while destructors (finalizers) clean up resources.

Example: Book Constructor

using System;

namespace LibrarySystem
{
    class Book
    {
        public string Title;
        public string Author;

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

        // Destructor (rarely used)
        ~Book()
        {
            Console.WriteLine($"Book {Title} is being destroyed.");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Book book = new Book("C# Unleashed", "John Doe");
            book = null; // Eligible for garbage collection
            GC.Collect(); // Force garbage collection (for demo)
        }
    }
}
  • Real-World Use: Initializing user profiles or database connections.

  • Pros:

    • Constructors ensure objects start in a valid state.

    • Destructors allow resource cleanup (rarely needed due to garbage collection).

  • Cons:

    • Multiple constructors can increase complexity.

    • Destructors are non-deterministic in .NET (garbage collector decides timing).

  • Best Practices:

    • Provide default and parameterized constructors.

    • Avoid complex logic in destructors; use IDisposable for resource cleanup.

  • Alternatives:

    • Factory methods for complex object creation.

    • IDisposable pattern for deterministic cleanup.


3. Properties and Fields

Fields store data, while properties provide controlled access with getters and setters.

Example: Book with Properties

using System;

namespace LibrarySystem
{
    class Book
    {
        private string _title; // Field

        // Property
        public string Title
        {
            get => _title;
            set => _title = string.IsNullOrWhiteSpace(value) ? "Unknown" : value;
        }

        // Auto-implemented property
        public string Author { get; set; }

        public Book(string title, string author)
        {
            Title = title;
            Author = author;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Book book = new Book("", "John Doe");
            Console.WriteLine($"Title: {book.Title}, Author: {book.Author}"); // Title: Unknown, Author: John Doe
        }
    }
}
  • Real-World Use: Managing product details with validation in e-commerce apps.

  • Pros:

    • Properties enable validation and encapsulation.

    • Auto-implemented properties reduce boilerplate.

  • Cons:

    • Overuse of properties for simple data can add complexity.

    • Public fields expose implementation details.

  • Best Practices:

    • Use properties over public fields.

    • Use auto-implemented properties for simple getters/setters.

    • Validate inputs in property setters.

  • Alternatives:

    • Public fields for simple structs (use sparingly).

    • Records for immutable properties.


4. Encapsulation

Encapsulation hides internal data and exposes only necessary functionality via properties or methods.

Example: Encapsulated Book

using System;

namespace LibrarySystem
{
    class Book
    {
        private string _title;
        private string _author;
        private bool _isCheckedOut;

        public string Title
        {
            get => _title;
            set => _title = string.IsNullOrWhiteSpace(value) ? "Unknown" : value;
        }

        public string Author { get; set; }

        public bool IsCheckedOut
        {
            get => _isCheckedOut;
            private set => _isCheckedOut = value;
        }

        public Book(string title, string author)
        {
            Title = title;
            Author = author;
            _isCheckedOut = false;
        }

        public void CheckOut()
        {
            if (!_isCheckedOut)
            {
                _isCheckedOut = true;
                Console.WriteLine($"{Title} checked out.");
            }
            else
            {
                Console.WriteLine($"{Title} is already checked out.");
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Book book = new Book("C# Unleashed", "John Doe");
            book.CheckOut(); // C# Unleashed checked out.
            book.CheckOut(); // C# Unleashed is already checked out.
        }
    }
}
  • Real-World Use: Protecting sensitive data like user credentials or inventory status.

  • Pros:

    • Prevents invalid state changes.

    • Improves maintainability by hiding implementation.

  • Cons:

    • Adds complexity with extra properties/methods.

    • Over-encapsulation can limit flexibility.

  • Best Practices:

    • Use private fields with public properties/methods.

    • Expose only necessary functionality.

  • Alternatives:

    • Records for immutable data with minimal encapsulation.

    • Structs for simple data without behavior.


5. Inheritance

Inheritance allows a class to inherit properties and methods from a base class.

Example: Library Items

using System;

namespace LibrarySystem
{
    class LibraryItem
    {
        public string Title { get; set; }
        public string Creator { get; set; }

        public virtual void DisplayInfo()
        {
            Console.WriteLine($"Item: {Title} by {Creator}");
        }
    }

    class Book : LibraryItem
    {
        public string ISBN { get; set; }

        public override void DisplayInfo()
        {
            Console.WriteLine($"Book: {Title} by {Creator}, ISBN: {ISBN}");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            LibraryItem item = new LibraryItem { Title = "Generic", Creator = "Unknown" };
            Book book = new Book { Title = "C# Unleashed", Creator = "John Doe", ISBN = "12345" };

            item.DisplayInfo(); // Item: Generic by Unknown
            book.DisplayInfo(); // Book: C# Unleashed by John Doe, ISBN: 12345
        }
    }
}
  • Real-World Use: Modeling product variants (e.g., physical vs. digital books).

  • Pros:

    • Promotes code reuse.

    • Simplifies modeling hierarchical relationships.

  • Cons:

    • Deep inheritance hierarchies can be complex.

    • Tight coupling between base and derived classes.

  • Best Practices:

    • Use virtual/override for extensible methods.

    • Prefer composition over deep inheritance.

  • Alternatives:

    • Composition for flexible relationships.

    • Interfaces for defining contracts.


6. Polymorphism (Overriding and Overloading)

Polymorphism allows objects to be treated as instances of their base class (overriding) or methods to have multiple signatures (overloading).

Example: Polymorphic Library

using System;

namespace LibrarySystem
{
    class LibraryItem
    {
        public string Title { get; set; }
        public virtual string GetDetails() => $"Title: {Title}";
    }

    class Book : LibraryItem
    {
        public string Author { get; set; }
        public override string GetDetails() => $"{base.GetDetails()}, Author: {Author}";
    }

    class Magazine : LibraryItem
    {
        public int Issue { get; set; }
        public override string GetDetails() => $"{base.GetDetails()}, Issue: {Issue}";
    }

    class Program
    {
        static void Main(string[] args)
        {
            LibraryItem[] items = new LibraryItem[]
            {
                new Book { Title = "C# Unleashed", Author = "John Doe" },
                new Magazine { Title = "Tech Weekly", Issue = 42 }
            };

            foreach (var item in items)
            {
                Console.WriteLine(item.GetDetails());
            }
            // Output:
            // Title: C# Unleashed, Author: John Doe
            // Title: Tech Weekly, Issue: 42
        }
    }
}
  • Real-World Use: Handling different product types in an inventory system.

  • Pros:

    • Overriding enables runtime polymorphism.

    • Overloading provides flexible method signatures.

  • Cons:

    • Overriding can lead to fragile base class issues.

    • Overloading may confuse users if signatures are similar.

  • Best Practices:

    • Use override for polymorphic behavior.

    • Keep overloaded methods distinct in purpose.

  • Alternatives:

    • Interfaces for contract-based polymorphism.

    • Default parameters instead of overloading.


7. Abstraction (Abstract Classes and Interfaces)

Abstraction hides implementation details, exposing only essential functionality.

Abstract Classes

Define partial implementations with abstract methods.

Interfaces

Define contracts without implementation.

Example: Library System

using System;

namespace LibrarySystem
{
    interface ICheckoutable
    {
        void CheckOut();
        bool IsCheckedOut { get; }
    }

    abstract class LibraryItem
    {
        public string Title { get; set; }
        public abstract string GetDetails();
    }

    class Book : LibraryItem, ICheckoutable
    {
        public string Author { get; set; }
        public bool IsCheckedOut { get; private set; }

        public override string GetDetails() => $"Book: {Title} by {Author}";
        public void CheckOut()
        {
            IsCheckedOut = true;
            Console.WriteLine($"{Title} checked out.");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Book book = new Book { Title = "C# Unleashed", Author = "John Doe" };
            book.CheckOut(); // C# Unleashed checked out.
            Console.WriteLine(book.GetDetails()); // Book: C# Unleashed by John Doe
        }
    }
}
  • Real-World Use: Defining common behavior for payment gateways or plugins.

  • Pros:

    • Abstract classes provide shared implementation.

    • Interfaces enforce contracts across unrelated classes.

  • Cons:

    • Abstract classes limit to single inheritance.

    • Interfaces require explicit implementation.

  • Best Practices:

    • Use interfaces for multiple inheritance or loose coupling.

    • Use abstract classes for shared base functionality.

    • Name interfaces with I prefix (e.g., ICheckoutable).

  • Alternatives:

    • Composition for flexible behavior.

    • Default interface methods (C# 8+) for implementation.


8. Static vs. Instance Members

Static members belong to the class, while instance members belong to objects.

Example: Library Counter

using System;

namespace LibrarySystem
{
    class Library
    {
        public static int TotalBooks { get; private set; } // Static property
        public string Name { get; set; } // Instance property

        public Library(string name)
        {
            Name = name;
            TotalBooks++;
        }

        public static void DisplayTotalBooks()
        {
            Console.WriteLine($"Total books in library: {TotalBooks}");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Library lib1 = new Library("City Library");
            Library lib2 = new Library("School Library");
            Library.DisplayTotalBooks(); // Total books in library: 2
            Console.WriteLine($"Library: {lib1.Name}"); // Library: City Library
        }
    }
}
  • Real-World Use: Tracking total users in an app or utility methods.

  • Pros:

    • Static members are shared across instances, saving memory.

    • Instance members allow per-object state.

  • Cons:

    • Static members can lead to global state issues.

    • Instance members increase memory usage.

  • Best Practices:

    • Use static for utility methods or shared state.

    • Avoid mutable static fields to prevent concurrency issues.

  • Alternatives:

    • Singleton pattern for single-instance classes.

    • Dependency injection for shared state.


Interactive Example: Library Management System

Let’s build a console-based library management system to apply OOP concepts.

using System;
using System.Collections.Generic;

namespace LibrarySystem
{
    interface ICheckoutable
    {
        void CheckOut();
        void Return();
        bool IsCheckedOut { get; }
    }

    abstract class LibraryItem
    {
        public string Title { get; set; }
        public abstract string GetDetails();
    }

    class Book : LibraryItem, ICheckoutable
    {
        private string _author;
        public string Author
        {
            get => _author;
            set => _author = string.IsNullOrWhiteSpace(value) ? "Unknown" : value;
        }
        public bool IsCheckedOut { get; private set; }

        public Book(string title, string author)
        {
            Title = title;
            Author = author;
            IsCheckedOut = false;
        }

        public override string GetDetails() => $"Book: {Title} by {Author}, {(IsCheckedOut ? "Checked Out" : "Available")}";

        public void CheckOut()
        {
            if (!IsCheckedOut)
            {
                IsCheckedOut = true;
                Console.WriteLine($"{Title} checked out.");
            }
            else
            {
                Console.WriteLine($"{Title} is already checked out.");
            }
        }

        public void Return()
        {
            if (IsCheckedOut)
            {
                IsCheckedOut = false;
                Console.WriteLine($"{Title} returned.");
            }
            else
            {
                Console.WriteLine($"{Title} is not checked out.");
            }
        }
    }

    class Library
    {
        public static int TotalItems { get; private set; }
        private List<LibraryItem> items = new List<LibraryItem>();

        public void AddItem(LibraryItem item)
        {
            items.Add(item);
            TotalItems++;
        }

        public void DisplayItems()
        {
            if (items.Count == 0)
            {
                Console.WriteLine("No items in library.");
                return;
            }
            foreach (var item in items)
            {
                Console.WriteLine(item.GetDetails());
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Library library = new Library();
            Book book1 = new Book("C# Unleashed", "John Doe");
            Book book2 = new Book("Design Patterns", "Jane Smith");

            library.AddItem(book1);
            library.AddItem(book2);

            while (true)
            {
                Console.WriteLine("\nLibrary Management System");
                Console.WriteLine("1. Display Items");
                Console.WriteLine("2. Check Out Book");
                Console.WriteLine("3. Return Book");
                Console.WriteLine("4. Exit");
                Console.Write("Choose an option: ");

                string choice = Console.ReadLine();
                if (choice == "4") break;

                switch (choice)
                {
                    case "1":
                        library.DisplayItems();
                        break;
                    case "2":
                        Console.Write("Enter book title to check out: ");
                        string title = Console.ReadLine();
                        var bookToCheckout = library.GetItems().Find(b => b.Title.Equals(title, StringComparison.OrdinalIgnoreCase));
                        if (bookToCheckout is ICheckoutable checkoutable)
                            checkoutable.CheckOut();
                        else
                            Console.WriteLine("Book not found or not checkoutable.");
                        break;
                    case "3":
                        Console.Write("Enter book title to return: ");
                        title = Console.ReadLine();
                        var bookToReturn = library.GetItems().Find(b => b.Title.Equals(title, StringComparison.OrdinalIgnoreCase));
                        if (bookToReturn is ICheckoutable returnable)
                            returnable.Return();
                        else
                            Console.WriteLine("Book not found or not returnable.");
                        break;
                    default:
                        Console.WriteLine("Invalid option!");
                        break;
                }
            }

            Library.DisplayTotalItems(); // Total items in library: 2
        }
    }

    // Extension for accessing private items list (for demo purposes)
    static class LibraryExtensions
    {
        public static List<LibraryItem> GetItems(this Library library) => library.items;
    }
}
  • How It Works:

    • Classes/Objects: Book and Library classes manage books and inventory.

    • Constructors: Initialize Book with title and author.

    • Properties/Fields: Encapsulate data with validation.

    • Encapsulation: Private fields and methods control access.

    • Inheritance: Book inherits from LibraryItem.

    • Polymorphism: GetDetails overridden for specific behavior.

    • Abstraction: LibraryItem abstract class and ICheckoutable interface.

    • Static Members: TotalItems tracks library-wide count.

  • Why It’s Useful: Mimics library systems like OverDrive or bookstore inventory.

  • Setup: Create a new Console App in Visual Studio or run dotnet new console.


Best Standards for Module 4

  • Classes/Objects: Use classes for complex entities; keep them focused.

  • Constructors/Destructors: Provide clear initialization; avoid destructors unless necessary.

  • Properties/Fields: Use properties for controlled access; avoid public fields.

  • Encapsulation: Hide internal state with private fields and public interfaces.

  • Inheritance: Favor shallow hierarchies; use composition when possible.

  • Polymorphism: Use virtual/override for extensibility; avoid overloading confusion.

  • Abstraction: Use interfaces for contracts, abstract classes for shared logic.

  • Static/Instance: Use static for shared utilities, instance for object-specific data.


Conclusion

You’ve just mastered object-oriented programming in C#! By learning classes, constructors, properties, encapsulation, inheritance, polymorphism, abstraction, and static vs. instance members, you’re ready to build structured, scalable applications. The library management system showcases how these concepts power real-world apps.

No comments:

Post a Comment

Thanks for your valuable comment...........
Md. Mominul Islam