OOPS

 

OOPS:

Object-Oriented Programming (OOP) is a programming paradigm that structures software design around objects, which encapsulate both data and methods. In C#, OOP facilitates the creation of modular, reusable, and maintainable code by modeling real-world entities and their behaviors.

By organizing code into classes and objects, OOP leverages key principles such as encapsulation, inheritance, polymorphism, and abstraction to enhance flexibility, reduce redundancy, and improve code management.

 

OOP offers several advantages:

It provides a clear structure for programs.

It adheres to the DRY (Don't Repeat Yourself) principle, making code easier to maintain, modify, and debug.

It enables the creation of reusable applications with less code and shorter development times.

In OOP, a class serves as a template for creating objects, while an object is an instance of a class. When objects are created, they inherit all the variables and methods defined in their class.

 

 

Class:

A class defines the structure of an object, including variables (or attributes) for storing data and methods (or functions) to perform operations on that data. It is a fundamental building block in object-oriented programming (OOP), providing a template for creating objects and encapsulating both data and behavior. By using classes, developers can create well-structured, maintainable, and reusable code.

 

Object:

In C#, an object is an instance of a class, created using the `new` keyword.

It represents the class's structure, including its data and methods. Each object has a unique state and behavior, with its own fields and methods as defined by the class. The `new` keyword allocates memory for the object and returns a reference that allows interaction with its data and methods.

 

 

Constructors in C#

A constructor is a special method in a class that is automatically invoked when an instance of the class is created. Its primary purpose is to initialize the object's data and set up any necessary state before the object is used.

 

Key points about constructors:

 

A constructor has the same name as the class and does not have a return type, not even void.

Constructors are used to set initial values for the object’s fields or to perform any setup operations needed before the object is used.

Constructors are invoked during object creation, allowing for proper initialization of the object.

Multiple constructors can be defined within a class, each with different parameters. This feature, known as constructor overloading, provides flexibility in object creation.

Types of Constructors:

Default Constructor: A parameterless constructor provided automatically by C# if no constructors are explicitly defined in the class. It initializes the object with default values. If you define any other constructors, the default constructor is no longer provided automatically.

 

Parameterized Constructor: A constructor that accepts parameters, allowing for customized initialization values when creating an object. This enables more control over the initial state of an object.

 

Static Constructor: A constructor used to initialize static members of the class. It cannot take parameters and is called automatically when the class is first accessed. This constructor is useful for setting up any static data or performing actions that need to occur only once per class, rather than per instance.

 

Primary Constructor (Introduced in C# 9.0):

A streamlined syntax for defining constructors that initializes properties directly within the constructor's parameters. This feature reduces boilerplate code by combining property declarations and initialization in a single statement.

Benefits: Primary constructors simplify object creation by reducing redundancy, especially for classes with simple initialization needs.

 

Primary Constructor Example:

public class Person(string name, int age)

{

    public string Name { get; } = name;

    public int Age { get; } = age;

}

 

 

 

 

Access modifiers:

Access modifiers in C# are used to control the visibility and accessibility of classes, methods, and other members. They play a crucial role in encapsulating data and protecting the integrity of an object’s state.

 

public:

Purpose: Allows unrestricted access to the class members from any part of the program, including other classes and assemblies.

Use Case: Use public for members that need to be accessible by other classes, such as methods or properties that provide essential functionality or data.

Example

public class Person

{

    public string Name { get; set; }

    public void Introduce()

    {

        Console.WriteLine($"Hello, my name is {Name}.");

    }

}

 

 

 

private:

Purpose: Restricts access to the class members so they can only be accessed within the same class. This ensures that internal data and operations are hidden from outside interference.

Use Case: Use private for internal data and helper methods that should not be exposed to other classes. It helps maintain the integrity of the object by controlling how its data is accessed and modified.

Example:

public class BankAccount

{

    private decimal balance;

 

    public void Deposit(decimal amount)

    {

        if (amount > 0)

            balance += amount;

    }

 

    public decimal GetBalance()

    {

        return balance;

    }

}

 

 

 

protected:

Purpose: Allows access to members from within the same class and from derived classes. It’s useful for creating class hierarchies where child classes need to access or modify base class members.

Use Case: Use protected when you want to expose data or methods to subclasses but not to other classes or external code.

Example:

public class Animal

{

    protected string species;

 

    public void SetSpecies(string species)

    {

        this.species = species;

    }

}

 

public class Dog : Animal

{

    public void DisplaySpecies()

    {

        Console.WriteLine($"This is a {species}."); // Accessible due to protected

    }

}

 

 

 

internal:

Purpose: Restricts access to members so they can only be accessed within the same assembly. This is useful for hiding implementation details from other assemblies while exposing them within the assembly.

Use Case: Use internal for classes and members that are intended to be used only within the same project or library.

Example:

internal class InternalHelper

{

    internal void Help()

    {

        Console.WriteLine("Helping with internal tasks.");

    }

}

 

 

 

protected internal:

Purpose: Combines protected and internal access levels, allowing access within the same assembly and in derived classes from any assembly.

Use Case: Use protected internal when you want to allow access to derived classes and classes within the same assembly, but not from external assemblies.

Example:

public class BaseClass

{

    protected internal string data;

 

    public BaseClass()

    {

        data = "Accessible within the same assembly or by derived classes.";

    }

}

 

public class DerivedClass : BaseClass

{

    public void ShowData()

    {

        Console.WriteLine(data); // Accessible due to protected internal

    }

}

 

 

 

private protected:

Purpose: Restricts access to members so they are only accessible within the containing class and by derived classes in the same assembly. This is a more restrictive combination of private and protected.

Use Case: Use private protected to limit access to derived classes within the same assembly, providing a high level of encapsulation while allowing some flexibility for inheritance.

Example:

public class BaseClass

{

    private protected string restrictedData;

 

    public BaseClass()

    {

        restrictedData = "Accessible only within this assembly and by derived classes.";

    }

}

 

public class DerivedClass : BaseClass

{

    public void ShowRestrictedData()

    {

        Console.WriteLine(restrictedData); // Accessible due to private protected

    }

}

 

 

 

Access modifiers in C# are essential for controlling how and where class members can be accessed. They help maintain encapsulation by defining clear boundaries around data and functionality. By choosing the appropriate access modifier, you ensure that your code remains secure, maintainable, and understandable.

 

 

 

 

Properties in C#:

In C#, properties are members of a class or struct that offer a controlled mechanism to read, write, or compute the values of private fields. They combine the simplicity of fields with the flexibility of methods, allowing data to be exposed and managed securely and efficiently. By using properties, you can encapsulate data, enforce validation, and enhance maintainability and security, leading to a more robust object-oriented design.

 

Components of a Property

1. Accessor Methods:

 

Getter (get): Retrieves the value of the property.

Setter (set): Assigns a value to the property.

 

public class Person

{

    private string name; // Private field

 

    public string Name   // Property

    {

        get { return name; }    // Getter

        set { name = value; }   // Setter

    }

}

 

 

 

2. Auto-Implemented Properties:

 

These simplify property declaration by eliminating the need for a private field. The compiler automatically creates a backing field.

Example:

public class Person

{

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

    public int Age { get; set; }      // Auto-implemented property

}

 

 

3. Read-Only and Write-Only Properties:

 

Read-Only: Only has a getter and cannot be set from outside the class.

Write-Only: Only has a setter and cannot be read from outside the class.

Examples:

public class Person

{

    private string name;

    private int birthYear;

 

    // Read-only property

    public string Name

    {

        get { return name; }

    }

 

    // Write-only property

    public int BirthYear

    {

        set { birthYear = value; }

    }

 

    // Constructor to set Name

    public Person(string name)

    {

        this.name = name;

    }

}

 

 

 

4. Expression-bodied Properties:

 

A shorthand syntax for properties with a single expression in the getter or setter.

Example:

public class Circle

{

    private double radius;

 

    public double Radius

    {

        get => radius;  // Expression-bodied getter

        set => radius = value;  // Expression-bodied setter

    }

 

    public double Diameter => 2 * radius;  // Read-only property with expression-bodied getter

}

 

 

 

 

5. Property with Logic:

 

Properties can include logic within the getter and setter to enforce constraints or perform additional operations.

 

 

public class BankAccount

{

    private decimal balance;

 

    public decimal Balance

    {

        get { return balance; }

        set

        {

            if (value < 0)

                throw new ArgumentException("Balance cannot be negative");

            balance = value;

        }

    }

}

 

 

 

6. Indexers:

 

Special properties that allow instances of a class to be indexed like arrays. Indexers can have different access levels for getters and setters.

Example:

public class PhoneBook

{

    private Dictionary<string, string> contacts = new Dictionary<string, string>();

 

    public string this[string name]

    {

        get

        {

            if (contacts.TryGetValue(name, out var number))

                return number;

            return "Contact not found";

        }

        set

        {

            contacts[name] = value;

        }

    }

}

 

 

Benefits of Using Properties

Encapsulation:

Properties allow for control over how data is accessed and modified, providing a way to enforce validation rules and maintain encapsulation.

 

Readability and Maintenance:

Properties improve code readability by providing a clear and consistent way to access and modify class data. They also make it easier to update the implementation later without changing the class's public interface.

 

Flexibility:

Properties can be used to compute values dynamically (read-only properties) or to implement complex logic (getters and setters) while maintaining a clean and intuitive syntax.

 

 

 

 

'as' Operator:

The as operator in C# provides a way to perform safe type casting, which avoids exceptions and allows you to handle cases where the cast might fail. It is particularly useful with reference types and nullable value types.

The as operator is a handy tool for working with types dynamically and helps in maintaining cleaner and safer code by avoiding unnecessary exceptions.

 

Safe Casting: The as operator attempts to cast an object to a specified type. If the cast is not possible, it returns null instead of throwing an exception.

Reference Types and Nullable Types: The as operator works with reference types and nullable types (e.g., int?). It does not work with non-nullable value types directly. To use as with value types, you need to use nullable types, like int?.

Avoiding Exceptions: Using as is preferable in situations where you are unsure of the object's type and want to avoid the overhead of handling exceptions like InvalidCastException.

 

Limitations

Non-nullable Value Types: You cannot use as to cast directly to non-nullable value types like int or double. For such cases, you must use explicit casting and handle possible exceptions or use the is keyword combined with conditional logic.

 

Performance Considerations: While using as is generally efficient, be mindful of performance impacts in critical code paths where type checking might be frequent.

 

 

Examples

Casting to Reference Type:

 

object obj = "Hello, World!";

string str = obj as string;  // str is "Hello, World!" because obj is a string

Casting to Nullable Type:

 

 

object obj = 123;

int? number = obj as int?;  // number is 123, since obj can be cast to int?

 

Handling Failure:

 

object obj = 123;

string str = obj as string;  // str is null because obj is not a string

if (str == null)

{

    Console.WriteLine("Cast failed.");

}

 

 

Example:

object obj = "Hello, World!";

string str = obj as string;  // Safe cast; str will be "Hello, World!"

 

Explanation:

The as operator helps preserve the original object’s type and allows checking if the cast is possible without altering the original object.

 

 

 

             

 

 

 

 

 

 

 

Encapsulation:

Encapsulation is the principle of bundling data (fields) and methods (functions) into a single unit, known as a class, and restricting direct access to some of its components. This is done using access modifiers to control visibility and access levels.

 

Encapsulation is a fundamental concept in object-oriented design that enhances code security, maintainability, and modularity. By hiding the internal state of an object and requiring interaction through public methods, encapsulation protects data integrity and provides a clear, controlled interface for class interaction.

 

Encapsulation improves code modularity by grouping related data and methods into a single class. This setup minimizes the impact of changes, ensuring that modifications to one part of the class have little to no effect on other parts of the program.

Encapsulation facilitates easier maintenance and updates by isolating the internal implementation of a class. Changes to how a class works internally do not affect other parts of the application that use the class, reducing the risk of unintended side effects.

 

 

The access modifiers used to implement encapsulation are:

 

Private: Restricts access to members of the class. Only the class itself can access its private fields and methods.

Public: Allows access to members from any part of the program.

Protected: Allows access to members from within the class and its derived classes.

Internal: Allows access to members within the same assembly.

Protected Internal: Allows access to members from within the same assembly or from derived classes.

 

 

Comments

Popular posts from this blog

Multiline to singleline IN C# - CODING

EF Core interview questions for beginners

EF Core interview questions for experienced