OOP: Polymorphism, Inheritance, Interfaces

5.0(1)
studied byStudied by 4 people
learnLearn
examPractice Test
spaced repetitionSpaced Repetition
heart puzzleMatch
flashcardsFlashcards
Card Sorting

1/81

encourage image

There's no tags or description

Looks like no tags are added yet.

Study Analytics
Name
Mastery
Learn
Test
Matching
Spaced

No study sessions yet.

82 Terms

1
New cards

What is Polymorphism?

Polymorphism is a fundamental concept in object-oriented programming (OOP) that allows objects to be treated as instances of their parent class, even if they are actually instances of derived classes. It enables a single function, method, or operator to operate in different ways based on the object or context, enhancing flexibility and reusability in code. Polymorphism is a generic concept that can be made concrete by using different types that come from that concept. There are 2 types of polymorphism:

  1. Compile-time Polymorphism (Static): Achieved through method overloading or operator overloading.

  2. Run-time Polymorphism (Dynamic): Achieved through method overriding using inheritance and interfaces.

2
New cards

What is inheritance?

Inheritance is an object-oriented programming (OOP) principle where a class (called the child or derived class) inherits attributes and behaviors (methods) from another class (called the parent or base class). This promotes code reusability, as common functionality in the parent class doesn’t need to be rewritten in derived classes, and it supports a hierarchical relationship between classes.

Example:

A Vehicle class may define common properties like speed and methods like start() and stop(). A Car class can inherit from Vehicle, gaining these properties and methods, and add its own unique features, such as openTrunk().

3
New cards

What kind of relationship does inheritance create between types?

It establishes an "is-a" relationship, like "a Dog is a Mammal."

4
New cards

What is a base class?

A base class is a class whose members are inherited (parent class).

5
New cards

What is a derived class?

It’s a (child) class that inherits members from another class (the parent class).

6
New cards

How can we make the base class members accessible in the derived class?

To make base class members accessible in a derived class, the members in the base class should have appropriate access modifiers, such as public, protected, or internal.

7
New cards

How can we override the implementation of a method or a property from the base class in the derived class?

To override the implementation of a method or property from the base class in a derived class, we need to mark the base method or property as virtual, and in the derived class to mark the same method or property as override:

public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("The animal makes a sound.");
    }
}

public class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("The dog barks.");
    }
}

8
New cards

How does the C# engine know which method to implement, if the method has a same name but different implementation in the base and derived class?

In C#, when a method in the base class is overridden in the derived class, the C# runtime uses dynamic dispatch to determine which method implementation to execute. This decision happens at runtime, allowing the derived class’s implementation to be called even when accessed through a base class reference. Here’s how it works:

  1. Virtual and Override Keywords: In the base class, the method to be overridden is marked with the virtual keyword, and in the derived class, the method is marked with override. This signals to the runtime that dynamic dispatch should be used.

  2. Polymorphism: When a base class reference holds a derived class object, C# checks the actual type of the object at runtime. If the method is overridden in the derived class, the derived class version is called. This is known as polymorphism.

  3. Method Table (VTable): Internally, C# uses a method table (or VTable) for classes with virtual methods. When the program runs, it references this table to locate the correct method based on the actual type of the object.

public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("The animal makes a sound.");
    }
}

public class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("The dog barks.");
    }
}

Animal myAnimal = new Dog();
myAnimal.Speak(); // Outputs: "The dog barks."

9
New cards

What are virtual methods and properties?

Virtual methods and properties in C# are members of a base class that can be (but doesn’t have to be) overridden in derived classes to provide specific implementations. By marking a method or property as virtual, you allow derived classes to customize or replace the behavior of these members while maintaining a consistent interface. This is a core feature of polymorphism in object-oriented programming.

If the method or property in question is not overridden, the implementation in the base class is being used.

10
New cards

What is method hiding?

Method hiding occurs in C# when a method in a derived class has the same name as a method in the base class, but the derived method does not use the override keyword. Instead, the derived class method "hides" the base class method. This is achieved using the new keyword, which explicitly indicates that the derived method is intentionally hiding the base method.

public class Animal
{
    public void Speak()
    {
        Console.WriteLine("The animal makes a sound.");
    }
}

public class Dog : Animal
{
    // Hiding the base class method using 'new'
    public new void Speak()
    {
        Console.WriteLine("The dog barks.");
    }
}

public class Program
{
    public static void Main()
    {
        Animal myAnimal = new Animal();
        myAnimal.Speak(); // Outputs: "The animal makes a sound."

        Dog myDog = new Dog();
        myDog.Speak(); // Outputs: "The dog barks."

        Animal animalDog = new Dog();
        animalDog.Speak(); // Outputs: "The animal makes a sound." (method from Animal, because the reference type is Animal)
    }
}

11
New cards

How can we define an inheritance hierarchy of more than 2 classes?

To define an inheritance hierarchy of more than two classes, you can create a chain of classes where each class derives from the one directly above it in the hierarchy. This forms a multi-level inheritance structure where each class inherits the members of its parent, gaining and potentially extending or modifying its behavior.

public class Animal
{
    public virtual void Move()
    {
        Console.WriteLine("The animal moves.");
    }
}

public class Mammal : Animal
{
    public override void Move()
    {
        Console.WriteLine("The mammal walks.");
    }
}

public class Dog : Mammal
{
    public override void Move()
    {
        Console.WriteLine("The dog runs.");
    }

    public void Bark()
    {
        Console.WriteLine("The dog barks.");
    }
}

public class Program
{
    public static void Main()
    {
        Animal genericAnimal = new Animal();
        genericAnimal.Move(); // Outputs: "The animal moves."

        Mammal mammal = new Mammal();
        mammal.Move(); // Outputs: "The mammal walks."

        Dog dog = new Dog();
        dog.Move(); // Outputs: "The dog runs."
        dog.Bark(); // Outputs: "The dog barks."
    }
}

12
New cards

Should we use inheritance hierarchy with more than 2 classes?

Using an inheritance hierarchy with more than two classes can be useful in some cases, but it should be approached carefully. Overly complex inheritance hierarchies can introduce challenges related to maintainability, flexibility, and performance, leading to what is known as the "fragile base class problem." A good rule of thumb is to use multi-level inheritance only when it truly simplifies your design and reflects a clear, real-world relationship.

13
New cards

Why can't a C# class derive directly from more than 1 base class?

In C#, a class cannot derive directly from more than one base class due to the complexity and ambiguity issues associated with multiple inheritance. This design choice avoids certain problems that arise when a class inherits from multiple classes at once.

Reasons for single inheritance in C#:

  1. Diamond Problem

  2. Increased Complexity

  3. Code Readability and Maintainability

14
New cards

What is the Diamond problem?

In multiple inheritance, a class may inherit from two base classes that themselves derive from a common ancestor, forming a "diamond" structure. This can create ambiguity about which implementation of a method or property the derived class should inherit. For example, if both base classes implement the same method differently, the compiler wouldn’t know which version to use in the derived class.

15
New cards

What is the System.Object class? Which methods does it contain?

The System.Object class, commonly referred to as Object, is the root of the C# class hierarchy. Every class in C# directly or indirectly inherits from System.Object, meaning all types in C# are derived from this class, including custom classes, arrays, and built-in types. Since Object is the base class of all types, it provides a set of fundamental methods that are available to every object, such as Equals(object obj), GetHashCode(), GetType(), ToString(), Finalize(), etc.

16
New cards

What is the ToString method? How does its basic implementation look like?

The ToString method in C# is a method in the System.Object class that returns a string representation of an object. Since ToString is defined in System.Object, every C# object has this method. By default, ToString returns the fully qualified name of the object's type, but it is often overridden in custom classes to provide more meaningful, human-readable information about the object.

The default ToString implementation in System.Object looks like this:

public virtual string ToString()
{
    return this.GetType().FullName;
}

Key Points of ToString:

  • Default Behavior: By default, ToString returns the full type name, which is often not very informative.

  • Overriding: It’s common practice to override ToString in custom classes to display useful information about the object, such as property values.

  • Automatic Calls: ToString is automatically called when you try to print an object (e.g., Console.WriteLine(object)) or concatenate it with a string.

Example of Overriding ToString:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    // Override ToString to display information about the person
    public override string ToString()
    {
        return $"Name: {Name}, Age: {Age}";
    }
}

public class Program
{
    public static void Main()
    {
        Person person = new Person { Name = "Alice", Age = 30 };
        Console.WriteLine(person.ToString()); // Outputs: "Name: Alice, Age: 30"
    }
}

17
New cards

Which constructor is called when an object from a derived class is created?

When an object of a derived class is created in C#, the constructor of the base class is called first, followed by the constructor of the derived class. This ensures that the base class is properly initialized before any derived class-specific initialization occurs. This process is known as constructor chaining, where the base class constructor is invoked automatically before the derived class's constructor executes. The derived class can pass arguments to the base class constructor using the base keyword.

Default constructor behavior:

  • If a base class has a parameterless constructor, it is automatically called before the derived class constructor.

  • If a base class does not have a parameterless constructor, the derived class constructor must explicitly call one of the base class's parameterized constructors.

18
New cards

How can we create constructors that set the state defined in the base and derived types?

We can do that by using the base keyword for the variables present in both constructors:


// Base Class: Represents a Company
public class Company
{
    public string CompanyName { get; private set; }
    public string Location { get; private set; }

    // Constructor to initialize the state of the Company
    public Company(string companyName, string location)
    {
        CompanyName = companyName;
        Location = location;
    }
}

// Derived Class: Represents an Employee who works at a Company
public class Employee : Company
{
    public string EmployeeName { get; private set; }
    public string Position { get; private set; }

    // Constructor explicitly passing Company attributes to the base constructor
    public Employee(string employeeName, string position, string companyName, string location)
        : base(companyName, location) // Pass Company attributes to the base class constructor
    {
        EmployeeName = employeeName; // Initialize Employee-specific attributes
        Position = position;
    }
}

// Usage Example
public class Program
{
    public static void Main()
    {
        // Create an Employee object
        Employee employee = new Employee("Alice Johnson", "Software Engineer", "TechCorp", "New York");

        // Display details
        Console.WriteLine($"Employee Name: {employee.EmployeeName}");
        Console.WriteLine($"Position: {employee.Position}");
        Console.WriteLine($"Company: {employee.CompanyName}");
        Console.WriteLine($"Location: {employee.Location}");
    }
}

19
New cards

What is the purpose of the base keyword?

The base keyword in C# is used to access members (constructors, methods, properties, or fields) of a base class from within a derived class. Its primary purpose is to ensure proper initialization of base class state and to invoke or override base class behavior when needed. The base keyword allows us to make the base class constructor responsible for initializing the fields and properties that all derived classes have in common.

20
New cards

What is implicit conversion?

Implicit conversion is a type of type conversion that is performed automatically by the C# compiler when it can guarantee that the conversion will not result in data loss or runtime errors. This typically occurs when converting from a smaller or more specific type to a larger or more general type, such as:

  • Numeric conversions: From a smaller numeric type to a larger numeric type (e.g., int to double).

  • Reference type conversions: From a derived class to a base class.

  • Interface implementations: From a class to one of its implemented interfaces.

public class Animal { }
public class Dog : Animal { }

Dog dog = new Dog();
Animal animal = dog; // Implicit conversion to base type


public interface IAnimal { }
public class Dog : IAnimal { }

Dog dog = new Dog();
IAnimal animal = dog; // Implicit conversion to interface

21
New cards

How are decimal and double used?

Both decimal and double are used for representing numeric values with fractional parts, but they serve different purposes due to their precision and range.

  • decimal has a higher precision (28-29 significant digits) and smaller range than double. It’s best used for financial and monetary calculations where accuracy is critical.

  • double has a lower precision (15-16 significant digits) and much larger range than decimal. It’s ideal to use for scientific and engineering calculations where performance and range are more important than precision.

decimal price = 19.99m; // 'm' suffix specifies a decimal literal
decimal total = price * 3;
Console.WriteLine(total); // Output: 59.97

double radius = 2.5;
double area = Math.PI * radius * radius;
Console.WriteLine(area); // Output: 19.6349540849362

22
New cards

What is explicit conversion?

Explicit conversion (also known as type casting) is a type of conversion that requires the programmer to explicitly specify the desired target type. This is necessary when there is a possibility of data loss or when the conversion between types is not automatically supported by the compiler.

double value = 9.99;
int intValue = (int)value; // Explicit conversion
Console.WriteLine(intValue); // Output: 9 (fractional part is truncated)

//Converting a base class to a derived one
public class Animal { }
public class Dog : Animal { }

Animal animal = new Dog();
Dog dog = (Dog)animal; // Explicit conversion

23
New cards

What are the key characteristics of explicit conversion?

  • Manual Cast: Requires the use of the cast operator (type).

  • Potential Data Loss: It may truncate data or cause runtime exceptions.

  • Runtime Checked: The conversion is checked during runtime, and invalid conversions can result in exceptions, or invalid behaviors of the program.

24
New cards

What is upcasting?

Upcasting is the process of converting a derived class object into its base class type. This is an implicit conversion that does not require a cast operator because the compiler can guarantee it is safe.

public class Animal
{
    public string Name { get; set; }

    public virtual void Speak()
    {
        Console.WriteLine("The animal makes a sound.");
    }
}

public class Dog : Animal
{
    public void WagTail()
    {
        Console.WriteLine("The dog wags its tail.");
    }

    public override void Speak()
    {
        Console.WriteLine("The dog barks.");
    }
}

public class Program
{
    public static void Main()
    {
        Dog dog = new Dog { Name = "Buddy" };

        // Upcasting: Dog to Animal
        Animal animal = dog;

        // Access members of Animal (base class)
        Console.WriteLine($"Animal Name: {animal.Name}");
        animal.Speak(); // Calls Dog's Speak method due to polymorphism

        // animal.WagTail(); // Compilation error: WagTail is not in the Animal class
    }
}
//Output
Animal Name: Buddy
The dog barks.

25
New cards

What are the key characteristics of upcasting?

  • Implicit Conversion: No explicit cast is required as the derived class "is-a" base class.

  • Polymorphism: The base class reference can point to a derived class object and access members declared in the base class.

  • Restricted Access: Only members available in the base class are accessible, even if the object is of the derived class type.

26
New cards

Why should we use upcasting?

Because of 2 reasons:

  • Polymorphism: Allows treating derived class objects as base class objects, enabling dynamic behavior using overridden methods.

  • Generic Code: Useful when working with collections or methods that operate on base class types but can handle derived class objects:

    List<Animal> animals = new List<Animal>
    {
        new Dog { Name = "Rex" },
        new Animal { Name = "Generic Animal" }
    };
    
    foreach (var animal in animals)
    {
        animal.Speak(); // Calls appropriate Speak method
    }

27
New cards

What are the limitations of upcasting?

  • Limited Access: Only base class members are accessible. Derived class-specific members (e.g., WagTail) are hidden unless you downcast.

  • Loss of Type Information: The object retains its runtime type, but the base class reference cannot access members or methods added in the derived class.

28
New cards

What is downcasting?

Downcasting is the process of converting a base class reference back into a derived class type. Unlike upcasting, downcasting requires an explicit cast because the compiler cannot guarantee that the base class reference actually refers to an object of the derived type at runtime.

If the object being downcasted is not of the target derived type, a runtime exception (InvalidCastException) will occur.

public class Animal
{
    public string Name { get; set; }

    public virtual void Speak()
    {
        Console.WriteLine("The animal makes a sound.");
    }
}

public class Dog : Animal
{
    public void WagTail()
    {
        Console.WriteLine("The dog wags its tail.");
    }

    public override void Speak()
    {
        Console.WriteLine("The dog barks.");
    }
}

public class Program
{
    public static void Main()
    {
        // Upcasting: Dog to Animal
        Animal animal = new Dog { Name = "Buddy" };

        // Downcasting: Animal back to Dog
        if (animal is Dog dog) // Safe way to downcast
        {
            dog.WagTail(); // Access Dog-specific member
            dog.Speak();   // Calls Dog's Speak method
        }
    }
}
//Output
The dog wags its tail.
The dog barks.

29
New cards

What are the key characteristics of downcasting?

  • Explicit Conversion: Requires the cast operator (type) to convert the base type reference to the derived type.

  • Runtime Check: The conversion is checked at runtime. If the cast is invalid, an exception is thrown.

  • Restores Access: Allows access to derived class-specific members that are otherwise hidden during upcasting.

30
New cards

When should we use downcasting?

  • Polymorphism: After upcasting, to restore access to derived class-specific members.

  • Runtime Behavior: When the exact runtime type needs to be determined to perform specific operations.

Downcasting should be avoided if possible.

31
New cards

What are the limitations of downcasting?

  • Runtime Risk: If the object is not of the target type, a direct cast can throw an InvalidCastException.

  • Type Dependency: Relies on knowing the object's runtime type, reducing flexibility.

32
New cards

How can we safely downcast?

By using:

  • is operator: Checks if the base reference is of the target derived type before casting.

    if (animal is Dog dog)
    {
        dog = (Dog)animal; //explicit cast
        dog.WagTail();
    }
  • as operator: Attempts to cast. If the cast is invalid, it returns null instead of throwing an exception.

    Dog dog = animal as Dog;
    if (dog != null)
    {
        dog.WagTail();
    }

    This operator can only be used to cast to nullable types (that means we can’t use it to cast to int for example, because int cannot be null)

33
New cards

What is the default value of uninitialized fields in classes?

In C#, when fields in a class are not explicitly initialized, they automatically take on their default values based on their type. These default values ensure that fields are always in a predictable state, even if you don't assign a value explicitly. For example, for int is 0.

34
New cards

What is the default value of objects?

The default value of an object in C# is null. This applies to any reference type, including instances of classes, arrays, delegates, or interfaces. When you declare an object without initializing it, it does not refer to any memory location and is null by default (it doesn’t contain any instance of any class).

35
New cards

How can we assign a null value to an object?

You can explicitly initialize an object to null if it has not yet been assigned a value. In this case you have to use the object type, instead of the var keyword:

Example obj = null; // Declare and assign null
var obj = null; // Won't work

36
New cards

What is the most common exception when it comes to null? How do we avoid it?

It’s trying to access a member of an object that’s null. This throws an exception, which is why we should always check for null values first, and then access class members:

if (obj != null)
{
    obj.SomeMethod();
}

obj?.SomeMethod(); // Executes only if obj is not null

37
New cards

Can all data types be assigned to null?

No. Some of them, like int, bool, and DateTIme cannot be assigned to null.

38
New cards

What is an abstract class?

An abstract class in C# is a class that cannot be instantiated directly and is meant to serve as a base class for other classes. It is designed to provide a common definition and optionally enforce certain behaviors through abstract members that must be implemented by derived classes.

39
New cards

What are abstract methods and properties?

Abstract methods and properties are members of an abstract class that do not have a body or implementation. They are meant to be implemented (overridden) by derived classes (abstract methods and properties are implicitly virtual), enforcing a specific structure or behavior across all subclasses.

public abstract class Appliance
{
    // Abstract property
    public abstract string Brand { get; }

    // Abstract method with an argument
    public abstract void TurnOn(string mode);
}

public class WashingMachine : Appliance
{
    // Override abstract property
    public override string Brand => "Whirlpool";

    // Override abstract method
    public override void TurnOn(string mode)
    {
        Console.WriteLine($"Washing Machine is now running in {mode} mode.");
    }
}

public class Program
{
    public static void Main()
    {
        Appliance appliance = new WashingMachine();
        Console.WriteLine($"Brand: {appliance.Brand}");   // Output: Brand: Whirlpool
        appliance.TurnOn("Quick Wash");                   // Output: Washing Machine is now running in Quick Wash mode.
    }
}

40
New cards

What are the key points we have to remember when using abstract methods and properties?

  • Abstract Methods and Properties Are Contracts: They define a contract that derived classes must fulfill, ensuring consistent behavior across all subclasses.

  • No Implementation in the Base Class: The abstract class only provides the declaration, leaving the implementation to the derived classes.

  • Overriding Is Mandatory: All non-abstract derived classes must provide an implementation for every abstract method or property.

  • Cannot Be private: Abstract methods and properties cannot be private because they must be accessible to derived classes for overriding.

41
New cards

Why do we need abstract methods?

Abstract methods are crucial for the following reasons:

  • Enforcing consistency: They ensure all derived classes adhere to a certain interface or contract.

  • Providing flexibility: They allow subclasses to define their own implementation details.

  • Supporting polymorphism: They enable the use of base class references to invoke derived class behavior.

  • Encouraging extensibility: They allow new classes to be added easily while enforcing a common structure.

42
New cards

How do abstract methods support polymorphism?

  • Abstract methods help to enable polymorphic behavior where you can refer to an object of a derived class using a base class reference.

  • This allows calling the abstract method on base class references, which will invoke the appropriate method from the derived class.

public abstract class Animal
{
    // Abstract method
    public abstract void Speak();
}

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

public class Cat : Animal
{
    // Custom implementation for Cat
    public override void Speak()
    {
        Console.WriteLine("Meow!");
    }
}


public class Program
{
    public static void Main()
    {
        Animal myDog = new Dog();
        Animal myCat = new Cat();

        myDog.Speak();  // Output: Woof!
        myCat.Speak();  // Output: Meow!
    }
}

43
New cards

What is the difference between abstract and non-abstract virtual methods? When should we use each of them?

  • Use an abstract method when you want to enforce that all derived classes must implement the method, and you do not want to provide any default behavior in the base class.

  • Use a non-abstract virtual method when you want to provide a default implementation in the base class that can be optionally overridden by derived classes.

<ul><li><p><strong>Use an abstract method</strong> when you want to <strong>enforce</strong> that all derived classes <strong>must</strong> implement the method, and you do not want to provide any default behavior in the base class.</p></li><li><p><strong>Use a non-abstract virtual method</strong> when you want to provide a <strong>default implementation</strong> in the base class that can be <strong>optionally overridden</strong> by derived classes.</p></li></ul><p></p>
44
New cards

What are sealed classes and methods?

In C#, sealed classes and methods are used to restrict inheritance and further overriding:

  • Sealed Classes: A class declared with the sealed keyword cannot be inherited by any other class. This is useful when you want to prevent others from extending your class.

  • Sealed Methods: A method in a base class that is declared with the sealed keyword cannot be overridden in derived classes. This is typically used with methods that are already marked as override.

class BaseClass
{
    public virtual void Show() { }
}

class DerivedClass : BaseClass
{
    public sealed override void Show()
    {
        // Implementation
    }
}

class FurtherDerivedClass : DerivedClass
{
    // Cannot override the Show method
}

45
New cards

What are the reasons for sealing a class or a method?

Sealed members or classes enhance code security and performance by restricting extensibility where it's unnecessary or could lead to misuse. Sealing can also cause some issues in testing.

46
New cards

Why are static classes implicitly sealed?

In C#, static classes are implicitly sealed because they are designed to contain only static members and cannot be instantiated or inherited, meaning they cannot be used as a base class.

47
New cards

Why is overriding of static methods not possible?

Overriding static methods is not possible in C# because static methods are not associated with any instance of a class. Instead, they are tied to the class itself. Overriding, on the other hand, is a feature of polymorphism, which works by deciding at runtime which instance method to call based on the type of the object.

48
New cards

What are extension methods?

Extension methods in C# are static methods that allow you to add new functionality to an existing type without modifying its source code or creating a new derived type. They are defined in a static class and use the this keyword as the first parameter to specify the type being extended.

public static class StringExtensions
{
    public static int WordCount(this string str)
    {
        return str.Split(new[] { ' ', '\t', '\n' }, StringSplitOptions.RemoveEmptyEntries).Length;
    }
}

// Usage:
string text = "Hello, extension methods!";
int count = text.WordCount(); // Outputs: 3

49
New cards

What are the key features of extension methods?

  • Non-Intrusive: They don't change the original type but provide additional methods as if they were part of the type.

  • Static in Nature: They are called like instance methods but implemented as static methods.

    //WordCount() is an extension method
    string text = "Hello, extension methods!";
    int count = text.WordCount(); // Outputs: 3
  • Readability: They make the code cleaner and easier to read.

50
New cards

How can we define an extension method?

  • Create a Static Class: Extension methods must be defined within a static class.

  • Create a Static Method: The method should be static and take the type you want to extend as the first parameter.

  • Use the this Keyword: Prefix the first parameter with this to indicate the type being extended:

    public static class MyExtensions
    {
        public static int WordCount(this string str)
        {
            return str.Split(new[] { ' ', '\t', '\n' }, StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }
  • A good practice is to have a separate class for each extended type

51
New cards

How can we use an extension method?

  • Ensure the Namespace is in Scope: If the extension method is defined in a different namespace, include it using the using directive.

  • Call It Like an Instance Method: Call the extension method directly on an object of the extended type:

    namespace MyExtensions
    {
        public static class StringExtensions
        {
            public static int WordCount(this string str)
            {
                return str.Split(new[] { ' ', '\t', '\n' }, StringSplitOptions.RemoveEmptyEntries).Length;
            }
        }
    }
    
    using MyExtensions; // Include the namespace containing the extension method
    
    class Program
    {
        static void Main()
        {
            string text = "Hello, extension methods!";
            int count = text.WordCount(); // Call the extension method
            Console.WriteLine(count);     // Outputs: 3
        }
    }

When using extension methods, the object (text in this case) is passed implicitly to the extension method as its first parameter.

52
New cards

What are the advantages of using extension methods?

The advantages of using extension methods in C# include:

  1. Enhanced Code Readability and Usability: Extension methods allow you to add methods to existing types, making them feel like a natural part of the type. Example: list.SortByLength() is more readable than calling a helper utility method like Utility.SortByLength(list).

  2. Non-Intrusive Customization: You can add functionality to classes you don’t own (e.g., .NET built-in classes like string or List<T>) without modifying their source code or inheriting them.

  3. Improved Code Maintainability: By grouping reusable logic into extension methods, you reduce code duplication and centralize updates in one place.

  4. Compatibility with LINQ: Extension methods are the foundation of LINQ. They allow query-like operations (Select, Where, OrderBy, etc.) on collections.

  5. Encapsulation of Utility Functions: Utility methods can be encapsulated directly into relevant types rather than being scattered in separate helper classes.

  6. Ease of Use: You can call extension methods using natural dot notation, making them easier to use compared to traditional static utility methods.

53
New cards

What are enums?

In C#, enums (short for enumerations) are a special data type that allows you to define a set of named constants, representing underlying integral values. Enums make code more readable and maintainable by using descriptive names instead of raw numbers.

public enum Days
{
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
}

54
New cards

What are the key features of enums?

  • Underlying Type: By default, the underlying type of enum values is int, starting from 0 and incrementing by 1. You can explicitly specify values or use a different underlying type (byte, short, etc.).

    public enum Status
    {
        Pending = 1,
        Approved = 2,
        Rejected = 3
    }
    
    Status currentStatus = Status.Approved;
    Console.WriteLine(currentStatus);       // Outputs: Approved
    Console.WriteLine((int)currentStatus); // Outputs: 2
  • Type Safety: Enums improve type safety by restricting values to the predefined set.

  • Implicit Conversion: Enum values can be cast to their underlying numeric type, and vice versa.

55
New cards

What are interfaces? Why do we need them?

We need interfaces to connect 2 types that seemingly don’t have a connection between them. In C#, interfaces are used to define a contract that classes or structs must follow. An interface contains method, property, event, or indexer signatures, but no implementation. A class or struct that implements an interface is required to provide the concrete implementation of those methods.

Classes that implement the same interface, share the same behavior:

public interface IDriveable
{
    void Drive();
}

public class Car : IDriveable
{
    public void Drive() { Console.WriteLine("Car is driving"); }
}

public class Truck : IDriveable
{
    public void Drive() { Console.WriteLine("Truck is driving"); }
}

public void TestDrive(IDriveable vehicle)
{
    vehicle.Drive();  // Can pass either Car or Truck
}

The naming convention for interfaces is a capital first letter I, followed by a verb describing the interface (first letter of the verb is also capital), followed by the -able suffix. (I + Drive + able)

56
New cards

What are the key reasons to use interfaces?

  1. Decoupling: Interfaces allow you to decouple code by separating the definition of methods from their implementation (The definition is in the interface, while the implementation is in the class implementing the interface).

  2. Multiple Inheritance: C# doesn’t support multiple class inheritance, but a class can implement multiple interfaces. This allows a class to inherit behavior from several sources.

  3. Polymorphism: Interfaces allow different classes to implement the same methods, enabling polymorphism. You can use a reference to the interface type, allowing different classes to be used interchangeably.

  4. Design Flexibility: Interfaces help create flexible and extensible designs. If you want to add a new feature to your system, you can add a new interface and have existing classes implement it without modifying their code.

  5. Testing and Mocking: Interfaces are commonly used in unit testing. You can mock interfaces to test the interactions with other parts of the system without needing real implementations, improving testability and reducing dependencies.

  6. Contract for Implementations: An interface defines a clear contract for what methods and properties a class must implement, improving consistency and ensuring that classes meet certain expectations.

57
New cards

What kind of a relationship do interfaces introduce between types?

The “behaves like” relationship.

58
New cards

How do we use interfaces in C#?

Firstly, we need to define the interface:

public interface IExample
{
    void DisplayMessage();
}

Then we need to implement the interface:

public class ExampleClass : IExample
{
    public void DisplayMessage()
    {
        Console.WriteLine("Hello from the interface implementation!");
    }
}

And finally, we use the interface:

IExample example = new ExampleClass();
example.DisplayMessage();

59
New cards

What do interfaces actually define?

It defines all the members that the data types implementing the interface are able to use. This is why all members in the interface have to be public — otherwise the classes implementing the interface won’t have access to these members, which will result with an exception. The methods defined in an interface are always implicitly virtual.

60
New cards

Can interfaces be instantiated?

No, interfaces in C# cannot be instantiated directly because they do not have an implementation. They are designed to define a contract that implementing classes or structs must fulfill.

61
New cards

Can a class implement multiple interfaces?

Yes, a class in C# can implement multiple interfaces. This allows a class to adhere to multiple contracts, enabling flexibility and modular design.

public interface IFirst
{
    void MethodA();
}

public interface ISecond
{
    void MethodB();
}

public class MultiInterfaceClass : IFirst, ISecond
{
    public void MethodA()
    {
        Console.WriteLine("MethodA from IFirst");
    }

    public void MethodB()
    {
        Console.WriteLine("MethodB from ISecond");
    }
}

// Usage
MultiInterfaceClass obj = new MultiInterfaceClass();
obj.MethodA(); // Output: MethodA from IFirst
obj.MethodB(); // Output: MethodB from ISecond

This feature is commonly used to achieve polymorphism and compose behavior from multiple sources.

62
New cards

What do interfaces contain most of the time?

Interfaces primarily serve to define what should be done without specifying how it should be done. In most cases they contain nothing but declarations of methods and properties. Interfaces cannot contain fields, constructors, destructors, or static members.

63
New cards

What is the difference between interfaces and abstract classes?

knowt flashcard image
64
New cards

What is JSON?

JSON (JavaScript Object Notation) is a lightweight data-interchange format that is easy to read, write, and parse. It is commonly used for transmitting data between a server and a client, such as in web APIs. It allows you to store objects in a textual format.

65
New cards

What is a library in programming?

In programming, a library is a collection of pre-written code that developers can use to perform common tasks or add specific functionality to their applications without having to write the code from scratch. Their key features are reusable code, specialized functionality (e.g. Math library), and language-specific (each language has its own libraries).

66
New cards

What are serialization and deserialization in C#?

Serialization and deserialization in C# refer to the processes of converting objects to and from specific formats for data storage or transmission. Serialization is the process of converting an object into a format that can be easily stored or transmitted, such as JSON, XML, or binary. Deserialization is the reverse process of converting the serialized data back into an object.

67
New cards

How can we serialize a C# object to JSON format?

By using the JsonConvert.SerializeObject method from the Newtonsoft.Json library or the built-in System.Text.Json.JsonSerializer class in .NET.

using System.Text.Json;

class Program
{
    static void Main()
    {
        var person = new { Name = "Alice", Age = 25 };
        string json = JsonSerializer.Serialize(person); // Serialize to JSON
        Console.WriteLine(json); // Output: {"Name":"Alice","Age":25}
    }
}

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

68
New cards

How can we deserialize a JSON string to a C# object?

By using the JsonConvert.DeserializeObject method from the Newtonsoft.Json library or the built-in System.Text.Json.JsonSerializer class in .NET.

using System.Text.Json;

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

class Program
{
    static void Main()
    {
        string json = "{\"Name\":\"Alice\",\"Age\":25}";
        Person person = JsonSerializer.Deserialize<Person>(json); // Deserialize
        Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
    }
}

69
New cards

How do we define the high-level logic of an application?

To define the logic of an app in a good way we should first write some pseudo-code (or in C# first write the classes and how we want the flow of the app to be), and then create the actual classes with the implementation details. We should also do the following:

  • Identify Key Functionalities: what the app will do

  • Break Down Requirements: divide functionalities in smaller tasks/components

  • Use abstractions: code should be written so we can see what the app does, not how it does it

  • Establish Workflows: how different parts of the code interact with each other

  • Select Patterns: choose a pattern for the app structure (e.g. MVC)

70
New cards

How do we easily create a well-designed program interface?

To easily create well-designed program interfaces:

  1. Follow SOLID Principles

  2. Use Abstract Classes and Interfaces: Define behaviors without dictating implementation. This promotes flexibility and reuse.

  3. Design for Extensibility: Ensure that interfaces can accommodate future changes without breaking existing implementations.

  4. Emphasize Consistency: Maintain uniform naming conventions, parameter orders, and return types to make interfaces intuitive.

  5. Document Clearly: Provide meaningful descriptions for each method, parameter, and expected outcome to improve usability.

A well-designed class should not affect any other part of the program when changes are made in it.

71
New cards

What is the Dependency Inversion Principle?

The Dependency Inversion Principle (DIP) states that:

  1. High-level modules (policies/rules) should not depend on low-level modules (details).

  2. Both should depend on abstractions (interfaces or abstract classes).

This ensures that changes in low-level details don't affect the high-level logic, promoting flexibility and reducing coupling in your code.

72
New cards

What is the Dependency Injection design pattern?

Dependency Injection is a design pattern where an object’s dependencies are provided (injected) from an external source rather than being created within the object itself. This design pattern promotes loose coupling by separating the object from its dependencies.

Dependencies are typically provided via:

  • Constructor Injection

  • Setter Injection

  • Interface Injection

73
New cards

What is the difference between the Dependency Inversion Principle and the Dependency Injection Design Pattern?

  1. Dependency Inversion Principle (DIP):

    • A principle in SOLID design that suggests depending on abstractions rather than concrete implementations.

    • Focuses on the direction of dependencies (high-level depends on abstractions, not low-level details).

  2. Dependency Injection (DI):

    • A design pattern to implement the Dependency Inversion Principle.

    • Focuses on how dependencies are provided (injected) into a class.

In essence: DIP is the theory; DI is a practical way to apply that theory.

74
New cards

What is a design pattern?

A design pattern is a reusable solution to a common problem in software design. The design patterns are a generalized solution with proven practices, and they also improve code quality.

75
New cards

What kind of design patterns are there?

  • Creational: Focus on object creation (e.g., Singleton, Factory, Builder).

  • Structural: Deal with class and object composition (e.g., Adapter, Decorator, Composite).

  • Behavioral: Focus on communication between objects (e.g., Observer, Strategy, Command).

76
New cards

What is coupling? Why is it important?

Coupling refers to the degree of dependency between components (classes, modules, or systems) in software design. The tighter components are coupled, the less reusable they are. Loose coupling (achieved through abstraction and Dependency Injection) improves maintainability, testability, and scalability.

77
New cards

What are target-typed new expressions?

Target-typed new expressions are a feature in C# that allows the type of an object to be inferred from its context, reducing redundancy when creating new objects.

//Regular expressions:
List<int> numbers = new List<int>(); // Type specified twice

// Target-typed expressions:
List<int> numbers = new(); // Type inferred from the variable declaration

78
New cards

What are generic types and methods?

Generic types and methods allow you to define classes, interfaces, or methods with a placeholder for the type of data they operate on. This enables type safety and reusability without specifying a concrete data type upfront.

public T GetDefaultValue<T>()
{
    return default(T);
}
// here T is the data type

Example usage:

var list = new List<int>(); // Generic type
int result = list[0]; // Type safety

79
New cards

What is the IEnumerable interface?

The IEnumerable interface is a foundational interface in C# that defines a mechanism for iterating over a collection of items. Any type that implements IEnumerable<Type> interface can be iterated with a foreach loop.

80
New cards

How can we reduce code repetition between related types in practice?

  • Inheritance: Extract shared logic into a base class and let related types inherit from it.

  • Interfaces: Define shared behavior through interfaces and implement them in related types.

  • Generics: Use generic classes or methods for reusable logic that works across types.

  • Extension Methods: Add shared functionality to existing types without modifying them.

81
New cards

What is LINQ?

LINQ (Language Integrated Query) is a library containing a set of methods in C# that allows querying and manipulating data in a declarative, SQL-like syntax directly within the language. It works with various data sources, including arrays, collections, databases, XML, and more.

var result = from item in collection
             where item > 5
             select item;

82
New cards

What is the Template Method Design Pattern?

The Template Method Design Pattern defines the skeleton of an algorithm in a method, deferring some steps to subclasses. It allows subclasses to redefine certain steps of the algorithm without changing its structure.

Key Points:

  1. Abstract Class: Contains the template method, which defines the overall sequence of steps.

  2. Concrete Subclasses: Implement specific steps of the algorithm while the template method controls the flow.

Example:

public abstract class AbstractClass
{
    public void TemplateMethod()
    {
        Step1();
        Step2();
        Step3();
    }

    protected abstract void Step1();
    protected abstract void Step2();
    protected void Step3() 
    { 
        Console.WriteLine("Step 3 is common for all.");
    }
}

public class ConcreteClass : AbstractClass
{
    protected override void Step1() 
    { 
        Console.WriteLine("Step 1 implemented in ConcreteClass."); 
    }
    protected override void Step2() 
    { 
        Console.WriteLine("Step 2 implemented in ConcreteClass."); 
    }
}