1/81
Looks like no tags are added yet.
Name | Mastery | Learn | Test | Matching | Spaced |
---|
No study sessions yet.
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:
Compile-time Polymorphism (Static): Achieved through method overloading or operator overloading.
Run-time Polymorphism (Dynamic): Achieved through method overriding using inheritance and interfaces.
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()
.
What kind of relationship does inheritance create between types?
It establishes an "is-a" relationship, like "a Dog is a Mammal."
What is a base class?
A base class is a class whose members are inherited (parent class).
What is a derived class?
It’s a (child) class that inherits members from another class (the parent class).
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
.
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.");
}
}
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:
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.
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.
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."
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.
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)
}
}
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."
}
}
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.
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#:
Diamond Problem
Increased Complexity
Code Readability and Maintainability
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.
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.
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"
}
}
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.
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}");
}
}
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.
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
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
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
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.
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.
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.
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
}
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.
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.
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.
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.
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.
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
)
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.
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).
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
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
Can all data types be assigned to null
?
No. Some of them, like int
, bool
, and DateTIme
cannot be assigned to null.
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.
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.
}
}
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.
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.
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!
}
}
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.
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
}
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.
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.
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.
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
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.
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
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.
What are the advantages of using extension methods?
The advantages of using extension methods in C# include:
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)
.
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.
Improved Code Maintainability: By grouping reusable logic into extension methods, you reduce code duplication and centralize updates in one place.
Compatibility with LINQ: Extension methods are the foundation of LINQ. They allow query-like operations (Select
, Where
, OrderBy
, etc.) on collections.
Encapsulation of Utility Functions: Utility methods can be encapsulated directly into relevant types rather than being scattered in separate helper classes.
Ease of Use: You can call extension methods using natural dot notation, making them easier to use compared to traditional static utility methods.
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
}
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.
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)
What are the key reasons to use interfaces?
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).
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.
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.
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.
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.
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.
What kind of a relationship do interfaces introduce between types?
The “behaves like” relationship.
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();
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.
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.
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.
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.
What is the difference between interfaces and abstract classes?
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.
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).
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.
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; }
}
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}");
}
}
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)
How do we easily create a well-designed program interface?
To easily create well-designed program interfaces:
Follow SOLID Principles
Use Abstract Classes and Interfaces: Define behaviors without dictating implementation. This promotes flexibility and reuse.
Design for Extensibility: Ensure that interfaces can accommodate future changes without breaking existing implementations.
Emphasize Consistency: Maintain uniform naming conventions, parameter orders, and return types to make interfaces intuitive.
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.
What is the Dependency Inversion Principle?
The Dependency Inversion Principle (DIP) states that:
High-level modules (policies/rules) should not depend on low-level modules (details).
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.
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
What is the difference between the Dependency Inversion Principle and the Dependency Injection Design Pattern?
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).
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.
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.
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).
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.
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
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
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.
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.
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;
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:
Abstract Class: Contains the template method, which defines the overall sequence of steps.
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.");
}
}