Skip to main content

Command Palette

Search for a command to run...

Mastering C# Part 7.1 Object Oriented Programming

Classes, Objects, Bindings, Constructor , this, Constructor Overloading

Updated
15 min read
Mastering C# Part 7.1 Object Oriented Programming
N

Hi, I'm Nishant Banjade, a Software Engineer at Ellucian, committed to transforming education through technology. With expertise in developing CRM systems, plugins, workflows, APIs, and customized solutions for Higher Education, I bring a strong focus on innovation and efficiency. Currently, I'm honing my skills in full-stack software development, system design, data structures, algorithms, and Microsoft Dynamics 365.

I am a skilled software developer with expertise in C#/.NET, D365, JavaScript, TypeScript, ReactJS, SQL, and cloud technologies like AWS, along with experience in RabbitMQ, Docker, MUI, Tailwind, JIRA, and Git.

Object-Oriented Programming (OOP) is a paradigm that structures software design around data, or objects, rather than functions and logic. In C#, a language inherently designed with OOP principles, this approach facilitates the modeling of real-world entities, leading to more modular, maintainable, and reusable code.

A class is a user-defined blueprint or prototype from which objects are created. Basically, a class combines the fields and methods(member function which defines actions) into a single unit. In C#, classes support polymorphism, inheritance and also provide the concept of derived classes and base classes.

Objects

It is a basic unit of Object-Oriented Programming and represents real-life entities. A typical C# program creates many objects, which as you know, interact by invoking methods. An object consists of :

  • State: It is represented by attributes of an object. It also reflects the properties of an object.

  • Behavior: It is represented by the methods of an object. It also reflects the response of an object with other objects.

  • Identity: It gives a unique name to an object and enables one object to interact with other objects.

When an object of a class is created, the class is said to be instantiated. All the instances share the attributes and the behavior of the class. But the values of those attributes, i.e. the state are unique for each object. A single class may have any number of instances.

As we declare variables like (type name;). This notifies the compiler that we will use the name to refer to data whose type is type. With a primitive variable, this declaration also reserves the proper amount of memory for the variable. So for reference variable, the type must be strictly a concrete class name.

Dog tuffy;

If we declare a reference variable(tuffy) like this, its value will be undetermined(null) until an object is actually created and assigned to it. Simply declaring a reference variable does not create an object.

Initializing an object

The new operator instantiates a class by allocating memory for a new object and returning a reference to that memory. The new operator also invokes the class constructor.

using System;
namespace Data_Types {
    // class declaration
    public class Dog{
        // Instance variable
        String name;
        String breed;
        int age;
        String color;
        // Constructor 
        public Dog(String name, String breed, int age, String color){
        this.name = name;
        this.breed = breed;
        this.age = age;
        this.color = color;
    }

        // Property 1

        public String GetName(){
            return name;
        }

        public String GetBreed(){
            return breed;
        }

        public int GetAge(){
            return age;
        }

        public String GetColor(){
            return color;
        }

        // Method 1
        public String ToString() {
            return ("Hi my name is "+this.GetName()+".\\nMy breed, age and color are "+this.GetBreed()+", "+this.GetAge()+", "+this.GetColor());
        }

    }

    class Data_Types {

        public static void Main(String[] args){
            Dog tuffy = new Dog("tuffy","papillon",5,"white");
            Console.WriteLine(tuffy.ToString());
        }
    }



}
  • Note how it works in low level

    when it comes to object allocation using the new keyword in C#, the memory is allocated on the heap.

    In C#, when you create an object using the new keyword, the memory for the object is allocated on the managed heap, not the stack.

    The managed heap is a region of memory managed by the Common Language Runtime (CLR), which is the underlying execution engine of .NET. It is used for allocating and deallocating memory for objects dynamically during runtime. The heap is responsible for managing the lifetime and memory usage of objects.

    On the other hand, the stack is used for storing local variables, method parameters, and other related data. It follows a Last-In-First-Out (LIFO) structure and is typically faster to allocate and deallocate memory compared to the heap.

    When you create an object in C#, memory for the object itself is allocated on the heap, and a reference to that memory location is stored on the stack or in another object, depending on how it is used.

      Dog dog = new Dog();
    

    The variable dog is a reference type, and the memory for the Dog object is allocated on the heap. The variable dog itself is stored on the stack and holds a reference to the memory location where the Dog object resides on the heap.

Types of classes

1. Static Class

  • Definition: A class declared with the static keyword.

  • Purpose: Contains only static members and is used to group related functionality.

  • Key Features:

    • Cannot be instantiated (no objects can be created).

    • All members must be static.

    • Useful for utility or helper methods.

Example

using System;

public static class MathHelper
{
    public static int Add(int a, int b)
    {
        return a + b;
    }

    public static int Multiply(int a, int b)
    {
        return a * b;
    }
}

class Program
{
    static void Main()
    {
        int sum = MathHelper.Add(5, 10);
        int product = MathHelper.Multiply(5, 10);

        Console.WriteLine($"Sum: {sum}, Product: {product}");
    }
}

Output:

Sum: 15, Product: 50

2. Partial Class

  • Definition: A class that can be split across multiple files using the partial keyword.

  • Purpose: Allows dividing large classes into smaller, manageable pieces.

  • Key Features:

    • All parts must have the partial keyword.

    • All parts are combined into a single class during compilation.

    • Useful in large projects or when auto-generated code is combined with custom code.

Example

File 1: EmployeePart1.cs

public partial class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
}

File 2: EmployeePart2.cs

public partial class Employee
{
    public double Salary { get; set; }

    public void Display()
    {
        Console.WriteLine($"Id: {Id}, Name: {Name}, Salary: {Salary}");
    }
}

Main Program

class Program
{
    static void Main()
    {
        Employee emp = new Employee { Id = 101, Name = "Alice", Salary = 50000 };
        emp.Display();
    }
}

Output:

Id: 101, Name: Alice, Salary: 50000

3. Abstract Class

  • Definition: A class that cannot be instantiated and may contain abstract methods.

  • Purpose: Serves as a base class, forcing derived classes to implement specific functionality.

  • Key Features:

    • Can have both abstract and non-abstract members.

    • Used for creating a contract for derived classes.

Example

public abstract class Animal
{
    public abstract void Speak(); // Abstract method
    public void Sleep() // Non-abstract method
    {
        Console.WriteLine("Sleeping...");
    }
}

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

class Program
{
    static void Main()
    {
        Animal myDog = new Dog();
        myDog.Speak();
        myDog.Sleep();
    }
}

Output:

Bark!
Sleeping...

4. Sealed Class

  • Definition: A class that cannot be inherited.

  • Purpose: Used to prevent further inheritance.

  • Key Features:

    • Declared with the sealed keyword.

    • Useful for security or performance-critical scenarios.

Example

public sealed class FinalClass
{
    public void Display()
    {
        Console.WriteLine("This is a sealed class.");
    }
}

class Program
{
    static void Main()
    {
        FinalClass obj = new FinalClass();
        obj.Display();
    }
}

5. Nested Class

  • Definition: A class declared inside another class.

  • Purpose: Encapsulates helper functionality relevant only to the outer class.

  • Key Features:

    • Can access private members of the containing class.

    • Helps organize code.

Example

public class OuterClass
{
    private int outerValue = 10;

    public class NestedClass
    {
        public void DisplayOuterValue(OuterClass outer)
        {
            Console.WriteLine($"Outer Value: {outer.outerValue}");
        }
    }
}

class Program
{
    static void Main()
    {
        OuterClass outer = new OuterClass();
        OuterClass.NestedClass nested = new OuterClass.NestedClass();
        nested.DisplayOuterValue(outer);
    }
}

Output:

Outer Value: 10

6. Generic Class

  • Definition: A class that can operate with any data type using type parameters.

  • Purpose: Provides type safety and reusability.

  • Key Features:

    • Declared with <T> for type parameter.

    • The type is specified during object creation.

Example

public class GenericClass<T>
{
    public T Value { get; set; }

    public void Display()
    {
        Console.WriteLine($"Value: {Value}");
    }
}

class Program
{
    static void Main()
    {
        GenericClass<int> intObj = new GenericClass<int> { Value = 42 };
        intObj.Display();

        GenericClass<string> stringObj = new GenericClass<string> { Value = "Hello" };
        stringObj.Display();
    }
}

Output:

Value: 42
Value: Hello

7. Anonymous Class

  • Definition: A class created without explicitly defining a class type.

  • Purpose: Useful for temporary storage of data.

  • Key Features:

    • Read-only properties.

    • Declared using var.

Example

class Program
{
    static void Main()
    {
        var person = new { Name = "Alice", Age = 30 };
        Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
    }
}

Output:

Name: Alice, Age: 30

Conclusion

  • Static Classes: Group utility methods.

  • Partial Classes: Divide large classes.

  • Abstract Classes: Create base classes with enforced contracts.

  • Sealed Classes: Prevent inheritance.

  • Nested Classes: Encapsulate inner logic.

  • Generic Classes: Provide type-safe reusable classes.

  • Anonymous Classes: Store temporary data

Early Binding and Late Binding in C#

In C#, binding refers to the process of connecting a method call to the method body. This can happen at compile time (early binding) or at runtime (late binding).


Early Binding

  • Definition: Early binding occurs when the method to be called is determined at compile time.

  • Also Known As: Static Binding or Compile-Time Binding.

  • How It Works: The compiler checks the method's existence and compatibility during compilation.

  • Performance: Faster because the method resolution happens at compile time.

  • Example: Most C# method calls using classes, interfaces, or generics are early bound.

Example of Early Binding

public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}

class Program
{
    static void Main()
    {
        Calculator calc = new Calculator();
        int result = calc.Add(5, 10); // Method resolved at compile time
        Console.WriteLine($"Sum: {result}");
    }
}

Output:

Sum: 15

Here, the method Add is resolved at compile time. The compiler knows which method to invoke.


Late Binding

  • Definition: Late binding occurs when the method to be called is determined at runtime.

  • Also Known As: Dynamic Binding or Runtime Binding.

  • How It Works: The decision about which method to invoke is deferred until the program runs.

  • Performance: Slower because method resolution happens at runtime.

  • Common Use Case: When working with dynamic types or reflection.

  • Example: Using the dynamic type or System.Reflection.

Example of Late Binding with dynamic

class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}

class Program
{
    static void Main()
    {
        dynamic calc = new Calculator(); // Dynamic type
        int result = calc.Add(5, 10);    // Method resolved at runtime
        Console.WriteLine($"Sum: {result}");
    }
}

Output:

Sum: 15

In this example, the type of calc and the method Add are determined during runtime.


Example of Late Binding with Reflection

using System;
using System.Reflection;

public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}

class Program
{
    static void Main()
    {
        Type type = typeof(Calculator);
        object obj = Activator.CreateInstance(type); // Create instance dynamically

        MethodInfo method = type.GetMethod("Add"); // Get method info
        int result = (int)method.Invoke(obj, new object[] { 5, 10 }); // Invoke method at runtime

        Console.WriteLine($"Sum: {result}");
    }
}

Output:

Sum: 15

Here, the Add method is invoked at runtime using reflection.


Key Differences Between Early and Late Binding

FeatureEarly BindingLate Binding
Resolution TimeCompile TimeRuntime
PerformanceFasterSlower
FlexibilityLess FlexibleMore Flexible
Error DetectionDetected at compile timeDetected at runtime
Use CaseCommon method calls, generics, etc.Reflection, dynamic types, COM objects

When to Use

  1. Early Binding:

    • Preferable when method calls are predictable.

    • Improves performance and compile-time type safety.

    • Example: Typical class and interface usage.

  2. Late Binding:

    • Useful when working with loosely typed objects (e.g., COM objects or dynamic libraries).

    • Necessary when the method or type is not known until runtime.

    • Example: Using reflection for plugins or dynamically loaded assemblies.


Conclusion

  • Use early binding wherever possible for better performance and compile-time error checking.

  • Use late binding only when you need runtime flexibility, such as dynamic types or plugins.

Constructor in C#

A constructor is a special method in C# used to initialize objects of a class. It is automatically called when an object is created and typically initializes fields or performs setup work.

Key Features:

  1. Same Name as the Class: A constructor must have the same name as the class.

  2. No Return Type: It does not return any value, not even void.

  3. Called Automatically: It is invoked automatically when an object is instantiated.

  4. Types of Constructors:

    • Default Constructor: No parameters, initializes fields to default values.

    • Parameterized Constructor: Accepts parameters to initialize fields with specific values.

    • Static Constructor: Initializes static members of the class. Called only once, when the class is first loaded.

    • Copy Constructor: Copies data from one object to another.


Example of Constructors

using System;

public class Person
{
    public string Name;
    public int Age;

    // Default Constructor
    public Person()
    {
        Name = "Unknown";
        Age = 0;
    }

    // Parameterized Constructor
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    // Copy Constructor
    public Person(Person existingPerson)
    {
        Name = existingPerson.Name;
        Age = existingPerson.Age;
    }

    public void Display()
    {
        Console.WriteLine($"Name: {Name}, Age: {Age}");
    }
}

class Program
{
    static void Main()
    {
        // Using Default Constructor
        Person person1 = new Person();
        person1.Display();

        // Using Parameterized Constructor
        Person person2 = new Person("Alice", 25);
        person2.Display();

        // Using Copy Constructor
        Person person3 = new Person(person2);
        person3.Display();
    }
}

Output:

Name: Unknown, Age: 0
Name: Alice, Age: 25
Name: Alice, Age: 25

The this Keyword in C

The this keyword refers to the current instance of the class. It is used to avoid ambiguity between class members and parameters or to invoke other constructors in the same class.

Key Uses of this:

  1. Access Instance Members: Refers to the current instance of a class.

  2. Avoid Name Ambiguity: Distinguishes between instance variables and method parameters with the same name.

  3. Invoke Another Constructor: Used to chain constructors within the same class.

  4. Pass the Current Instance: Can pass the current object to another method or constructor.


Examples of this:

1. Avoid Ambiguity

public class Employee
{
    private string name;

    public Employee(string name)
    {
        this.name = name; // 'this' distinguishes between the class field and parameter
    }

    public void Display()
    {
        Console.WriteLine($"Name: {name}");
    }
}

2. Chaining Constructors

public class Product
{
    public string Name;
    public double Price;

    // Default Constructor
    public Product() : this("Unknown", 0.0) { }

    // Parameterized Constructor
    public Product(string name, double price)
    {
        this.Name = name;
        this.Price = price;
    }

    public void Display()
    {
        Console.WriteLine($"Product: {Name}, Price: {Price}");
    }
}

class Program
{
    static void Main()
    {
        Product defaultProduct = new Product();
        defaultProduct.Display();

        Product specificProduct = new Product("Laptop", 1200.50);
        specificProduct.Display();
    }
}

Output:

Product: Unknown, Price: 0
Product: Laptop, Price: 1200.5

3. Passing the Current Instance

public class Order
{
    public int OrderId;
    public double Amount;

    public Order(int orderId, double amount)
    {
        this.OrderId = orderId;
        this.Amount = amount;
    }

    public void ProcessOrder()
    {
        OrderProcessor.Process(this); // Passing the current instance
    }
}

public static class OrderProcessor
{
    public static void Process(Order order)
    {
        Console.WriteLine($"Processing Order #{order.OrderId}, Amount: {order.Amount}");
    }
}

class Program
{
    static void Main()
    {
        Order order = new Order(101, 250.75);
        order.ProcessOrder();
    }
}

Output:

Processing Order #101, Amount: 250.75

Key Differences Between Constructor and this:

FeatureConstructorthis Keyword
PurposeInitializes a new object.Refers to the current instance of a class.
TypeSpecial method with no return type.A keyword for referencing.
UsageCreates or initializes an object.Avoids ambiguity or chains constructors.
Access to InstanceImplicitly works with instance members during object creation.Explicitly refers to instance members or constructors.

Summary

  • Constructor: Initializes objects, with options for default, parameterized, or copy initialization.

  • this Keyword: Resolves ambiguity, invokes constructors, or passes the current instance of a class.

Constructor Overloading in C#

Constructor Overloading is a concept in C# where a class can have multiple constructors with the same name but different parameter lists. This allows creating objects of the class in different ways, depending on the information provided during object creation.


Key Points

  • Name: All constructors in a class share the same name as the class.

  • Overloading: Differentiation is based on the number, types, or order of parameters.

  • Usage: Provides flexibility in object initialization by allowing different ways to set up an object.


Example of Constructor Overloading

using System;

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public double Salary { get; set; }

    // Default constructor
    public Employee()
    {
        Id = 0;
        Name = "Unknown";
        Salary = 0.0;
    }

    // Constructor with one parameter
    public Employee(int id)
    {
        Id = id;
        Name = "Unknown";
        Salary = 0.0;
    }

    // Constructor with two parameters
    public Employee(int id, string name)
    {
        Id = id;
        Name = name;
        Salary = 0.0;
    }

    // Constructor with three parameters
    public Employee(int id, string name, double salary)
    {
        Id = id;
        Name = name;
        Salary = salary;
    }

    public void Display()
    {
        Console.WriteLine($"Id: {Id}, Name: {Name}, Salary: {Salary}");
    }
}

class Program
{
    static void Main()
    {
        // Using default constructor
        Employee emp1 = new Employee();
        emp1.Display();

        // Using constructor with one parameter
        Employee emp2 = new Employee(101);
        emp2.Display();

        // Using constructor with two parameters
        Employee emp3 = new Employee(102, "Alice");
        emp3.Display();

        // Using constructor with three parameters
        Employee emp4 = new Employee(103, "Bob", 50000.50);
        emp4.Display();
    }
}

Output

Id: 0, Name: Unknown, Salary: 0
Id: 101, Name: Unknown, Salary: 0
Id: 102, Name: Alice, Salary: 0
Id: 103, Name: Bob, Salary: 50000.5

How It Works

  1. Default Constructor:

    • No parameters.

    • Initializes the object with default values.

  2. Parameterized Constructors:

    • Accept one or more parameters to initialize the object with specific values.
  3. Flexibility:

    • You can initialize the object in different ways depending on the information available during creation.

Constructor Overloading with Chaining

You can call one constructor from another using the this keyword to avoid repetitive code.

Example with Chaining

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public double Salary { get; set; }

    // Constructor with chaining
    public Employee() : this(0, "Unknown", 0.0) { }

    public Employee(int id) : this(id, "Unknown", 0.0) { }

    public Employee(int id, string name) : this(id, name, 0.0) { }

    public Employee(int id, string name, double salary)
    {
        Id = id;
        Name = name;
        Salary = salary;
    }

    public void Display()
    {
        Console.WriteLine($"Id: {Id}, Name: {Name}, Salary: {Salary}");
    }
}

Here, all constructors eventually call the most detailed constructor, avoiding code duplication.


Benefits of Constructor Overloading

  1. Flexibility: Allows creating objects in different ways depending on available information.

  2. Code Reuse: Reduces repetitive code using constructor chaining.

  3. Clarity: Makes object initialization explicit and easy to understand.


Conclusion

Constructor overloading is a powerful feature in C# that enhances flexibility and code reuse. By providing multiple ways to initialize an object, you can make your classes adaptable to various initialization scenarios.

Mastering C# Part 7.1 Object Oriented Programming