csharp-interview-question-and-answer

C# Interview Questions and Answers (2025 Guide)

Whether you’re preparing for C# interview questions in 2025—or just looking to refresh your skills—you’re in the right place. C# interview questions are a top priority for both new and experienced developers who want to stand out in the .NET world. That’s because C# as it’s often called—remains one of Microsoft’s most sought-after coding languages.

These C# interview questions will help you review the key concepts you need to know, and build your confidence in the process. Whether you’re just starting out or looking to move up the career ladder, this guide covers the topics you’ll need to ace your next interview: object-oriented programming (OOPs), error handling, collections, LINQ, async/await—and many other essential skills.

Going through these C# interview questions will give you a solid foundation to tackle technical interviews with confidence. And show you just how well you know your stuff.


Table of Contents

1.What is C# and how is it different from C++ and Java?

C# as many people call it—is a modern, object-oriented programming language from Microsoft. Its main use is building applications on the .NET platform. That can be anything from web and desktop applications to mobile and cloud-based solutions.

The difference between C# and C++ is pretty straightforward. C# is a lot easier and safer to work with. You don’t have to manage memory manually in C#—garbage collection handles that for you. That means you’re less likely to run into bugs like memory leaks or pointer errors. (And less likely to pull your hair out in the process.) That’s the difference between driving a stick shift (C++) and an automatic (C#): less control, but way more convenience.

C# and Java are similar in many ways. Both use garbage collection, have similar syntax and are object-oriented. But C# feels more modern because it has some extra features—properties, events, async/await for asynchronous programming, and tighter integration with Windows and Microsoft technologies. Java is the all-rounder, used across many platforms. C# is the go-to choice when you’re working in the Microsoft ecosystem.

C# takes the best of C++ and Java, adds its own flair and makes it a great choice for building modern applications of all kinds.


2.What is the difference between public, private, protected, and internal access modifiers in C#?

Access modifiers in C# control who can see or use a class, method, or variable. Think of them like privacy settings on social media—who gets to view your content. Here’s a simple breakdown:

  • public
    This is the most open setting. When something is marked as public, it can be accessed from anywhere—inside the class, outside the class, in other classes, or even from other projects. It’s like posting a public status on Facebook—anyone can see it.
  • private
    This is the most restricted. private members can only be accessed within the same class. It’s like a locked diary—you can read it, but nobody else can.
  • protected
    A protected member is accessible within the same class and by derived (child) classes. Think of it like a family recipe—your kids can have it, but the neighbors can’t.
  • internal
    This one is a bit special. internal means the code is accessible only within the same project or assembly. It’s like sharing a file with your coworkers in the same office—it stays within your company.

You can also mix some of these, like protected internal, which means it can be accessed within the same project OR by child classes, even if they’re in a different project.


3.What are the main features of C#?

C# is packed with features that make it powerful, modern, and easy to work with. Whether you’re building web apps, desktop software, games, or cloud services—C# has your back. Here are some of its standout features:

  • Object-Oriented
    C# is fully object-oriented, which means everything revolves around classes and objects. This helps you write clean, modular, and reusable code.
  • Type-Safe
    It keeps a close eye on data types. You can’t accidentally mix apples with oranges (like a string with an integer), which reduces bugs.
  • Automatic Memory Management (Garbage Collection)
    You don’t have to worry about cleaning up memory manually. C# has a garbage collector that takes care of that behind the scenes.
  • Rich Standard Library
    C# comes with a huge set of built-in classes and libraries that make everyday tasks like file handling, data processing, and networking super easy.
  • LINQ (Language Integrated Query)
    LINQ lets you query data (like from arrays, databases, or XML) in a clean and readable way—just like writing a sentence.
  • Asynchronous Programming (async/await)
    This makes writing fast and responsive apps easier. You can do things in the background (like fetching data from the internet) without freezing your app.
  • Cross-Platform with .NET Core / .NET 5+
    C# isn’t just for Windows anymore! Thanks to .NET Core and later versions, you can build apps that run on Windows, macOS, and Linux.
  • Strong Community and Microsoft Support
    Being developed and maintained by Microsoft means C# is regularly updated with new features, and there’s a huge community to help when you’re stuck.

In short, C# is a modern, safe, and versatile language that keeps evolving to meet today’s development needs


4.Explain the concept of managed and unmanaged code in C#.

Alright, so when we talk about managed and unmanaged code in C#, we’re basically talking about who’s in charge of memory and resource management—you or the .NET runtime.

  • Managed code is the code that runs under the supervision of the .NET Common Language Runtime (CLR). The CLR takes care of a lot of things for you, like memory allocation, garbage collection (cleaning up unused objects), security checks, and exception handling. When you write C# code, you’re usually writing managed code by default. It’s like having a safety net—you focus on writing logic, and the runtime manages the low-level stuff.
  • Unmanaged code, on the other hand, is code that runs outside the control of the CLR. This includes code written in languages like C or C++, where you’re responsible for things like manually allocating and freeing memory. It gives you more control and potentially more performance—but also more risk (like memory leaks or crashes).

Now, even in a C# application, you might sometimes use unmanaged code. This usually happens when you’re calling external libraries, using interop (P/Invoke) to work with Windows APIs or legacy code written in C/C++. In those cases, you’re stepping outside the safety net, and it’s on you to handle memory and resource cleanup properly.

Quick summary:

  • Managed Code → Controlled by CLR, safer, automatic memory management.
  • Unmanaged Code → Outside CLR, more control, but also more responsibility.

5.What is the CLR in .NET?

The CLR, or Common Language Runtime, is basically the heart of the .NET Framework. It’s the engine that runs your C# (or any .NET language) code.

Think of it like this: when you write C# code, you’re not directly writing machine-level instructions. Instead, your code gets compiled into something called Intermediate Language (IL). When you run your application, the CLR steps in and takes that IL code, compiles it into machine code, and executes it on the fly. This process is handled by something called the Just-In-Time (JIT) compiler.

But the CLR doesn’t just run your code—it manages a lot of things behind the scenes to make development easier and more secure. For example:

  • It handles memory management (via garbage collection)
  • Manages threading and execution
  • Provides security features like code access security
  • Handles exception handling
  • Supports language interoperability, so you can use multiple .NET languages in one app

In short, the CLR makes sure your code runs efficiently and safely, while taking care of a bunch of complex tasks you’d otherwise have to manage yourself.


6.What is the difference between == and .Equals() in C#?

In C#, both == and .Equals() are used to compare two values, but they don’t always behave the same way, especially when you’re working with objects.

Let’s break it down:

  • == (Equality Operator):
    This operator checks for value equality for built-in types (like numbers, bool, etc.).
    But for objects (like classes), == by default checks if both references point to the exact same object in memory—unless the class overrides the == operator (like strings do).
  • .Equals() Method:
    This is a method defined in the base Object class, and it’s meant to be overridden to define what it means for two instances to be “equal”. It usually checks if the values or content inside two objects are the same, not whether they are the exact same object.
string a = "hello";
string b = "hello";

Console.WriteLine(a == b);       // True – because strings override `==` to compare content
Console.WriteLine(a.Equals(b));  // True – also compares content

But now look at this with custom objects:

MyClass obj1 = new MyClass();
MyClass obj2 = new MyClass();

Console.WriteLine(obj1 == obj2);       // False – different references
Console.WriteLine(obj1.Equals(obj2));  // False – unless you override Equals()

In summary:

  • Use == when you’re comparing primitive types or if you’re sure the type has overridden the operator.
  • Use .Equals() when you want to be explicit, or when working with objects and you’ve customized equality logic.

7.What are value types and reference types in C#?

In C#, everything you work with is either a value type or a reference type, and the main difference comes down to how data is stored and accessed in memory.

Value Types

Value types store the actual data directly in memory. When you assign a value type to another variable, a copy of the data is made. They live on the stack, which is a fast and lightweight part of memory.

Examples: int, float, bool, char, struct, enum

int x = 10;
int y = x;
y = 20;

Console.WriteLine(x); // Output: 10 (x didn’t change because y is a copy)

Reference Types

Reference types, on the other hand, store a reference (or pointer) to the actual data, which lives on the heap (a larger, shared memory space). When you assign a reference type to another variable, both variables point to the same object in memory.

Examples:
class, interface, array, string, delegate, object

MyClass a = new MyClass();
MyClass b = a;
b.Value = 5;

Console.WriteLine(a.Value); // Output: 5 (a and b point to the same object)

8.What is boxing and unboxing in C#?

Boxing and unboxing are ways to convert between value types and reference types in C#. It’s a common concept that comes up when you’re dealing with things like collections, interfaces, or object-based operations.

Boxing

Boxing is when a value type (like int, bool, etc.) is converted into a reference type, typically an object.

Basically, the value gets wrapped inside an object and stored on the heap instead of the stack.

Example:

int number = 42;
object boxed = number;  // Boxing happens here

Now number is a value type, but boxed is a reference type holding a copy of that value.

Unboxing

Unboxing is the reverse—it’s when you convert a reference type back to a value type.

You have to explicitly cast the object back to its original value type.

Example:

object boxed = 42;
int number = (int)boxed;  // Unboxing happens here

Important Notes:

  • Boxing/unboxing comes with a performance cost because it involves memory allocation and copying.

9.What is the difference between const, readonly, and static in C#?

These three keywords—const, readonly, and static—are often used for defining data that doesn’t change (or doesn’t need to be recreated), but they each have their own rules and use cases. Let’s break them down:

Constant

  • A const is a compile-time constant.
  • You must assign its value when you declare it, and it can never change.
  • It’s implicitly static, so you don’t need to mark it as static.

Use when: the value is fixed and known at compile time (like Pi or number of days in a week).

Example:

const int DaysInWeek = 7;

readonly

A readonly field can be set:

  • At the time of declaration, OR
  • In the constructor of the class.

Once set, the value can’t be changed.

It’s useful when the value isn’t known at compile time, but shouldn’t change afterward.

Use when: the value is fixed after object creation but needs to be calculated or passed in at runtime.

Example:

readonly string createdBy;

public MyClass(string user)
{
    createdBy = user; // allowed in constructor
}

static

  • A static member belongs to the type itself, not to an instance.
  • It’s shared across all objects of the class.
  • Can be used with variables, methods, constructors, etc.

Use when: you need a shared value or behavior that doesn’t rely on individual instances.

Example:

static int counter = 0;


10.What are access modifiers in C#?


11.What is the difference between ref and out parameters?

Both ref and out are used when you want to pass variables by reference to a method, but they’re used a bit differently depending on your goal.

Let’s break it down:

ref (Reference)

  • The variable must be initialized before it’s passed to the method.
  • The method can read and modify the value.
  • Think of it like saying:
    “Hey method, here’s my variable—feel free to update it.”

Example:

void AddTen(ref int number)
{
    number += 10;
}

int x = 5;
AddTen(ref x);
Console.WriteLine(x); // Output: 15

out (Output)

  • The variable doesn’t need to be initialized before it’s passed.
  • The method must assign a value before the method ends.
  • It’s usually used when a method needs to return multiple values.

Example:

void GetUser(out string name, out int age)
{
    name = "Satyaprakash";
    age = 25;
}

string userName;
int userAge;
GetUser(out userName, out userAge);
Console.WriteLine($"{userName}, {userAge}"); // Output: Satyaprakash, 25

Tip:

  • Use ref when the input matters and might change.
  • Use out when you just want to return extra data from a method.

12.What is method overloading and method overriding in C#?

Both method overloading and method overriding are ways to achieve polymorphism in C#, but they work in different ways and serve different purposes.

Let’s break them down:

Method Overloading

  • Happens in the same class.
  • You define multiple methods with the same name but different parameters (different number, type, or order).
  • The compiler decides which method to call based on the arguments you pass (this is compile-time polymorphism).

Example:

public void Print(string message) { }
public void Print(string message, int times) { }

Here, both methods are called Print, but they take different parameters.

Method Overriding

  • Happens in inheritance (i.e., between base and derived classes).
  • The base class defines a method as virtual, and the derived class overrides it using the override keyword.
  • This lets you change or extend the behavior of the base class method (this is run-time polymorphism).

Example:

public class Animal
{
    public virtual void Speak() => Console.WriteLine("Animal sound");
}

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

When you call Speak() on a Dog, it will say “Bark” instead of “Animal sound.”

Note;

  • Overloading = Same method name, different inputs
  • Overriding = Same method, different behavior in a subclass

13.What is the difference between an abstract class and an interface?

This is one of the most common C# interview questions—and it’s an important one! Both abstract classes and interfaces are used to define a contract or blueprint for other classes, but they do it in different ways and are used in different scenarios.

Let’s break it down

Abstract Class

  • An abstract class is a class that can have both implemented and unimplemented (abstract) methods.
  • You can’t create an object from it directly.
  • It can contain:
    • Fields
    • Constructors
    • Methods (both abstract and concrete)
    • Properties
    • Access modifiers (like public, protected, etc.)
public abstract class Animal
{
    public abstract void Speak();  // No body
    public void Eat() => Console.WriteLine("Eating...");
}

Interface

  • An interface is a completely abstract contract.
  • It only contains method signatures (no implementation—until C# 8 added default interface methods).
  • A class can implement multiple interfaces (C# allows multiple inheritance through interfaces).

Example:

public interface IAnimal
{
    void Speak();
}

Key Differences:

FeatureAbstract ClassInterface
InheritanceSingle inheritance onlyMultiple interfaces can be implemented
Method ImplementationCan have both abstract & normal methodsNo implementation (except default methods in C# 8+)
ConstructorsCan have constructorsCannot have constructors
Fields/StateCan have fields and propertiesCannot have fields (only properties)
Access ModifiersAllowedNot allowed (everything is public by default)

When to Use What?

Use an abstract class when:

  • You want to share common logic.
  • You need base constructors or fields.
  • There’s a strong “is-a” relationship.

Use an interface when:

  • You want to define a contract without enforcing inheritance.
  • You need to support multiple inheritance.
  • You just want to say: “Any class that implements this must provide these behaviors.”

14.What are extension methods in C#?

xtension methods in C# let you add new methods to existing types—like classes or interfaces—without modifying their source code or creating a new derived class. It’s like giving extra powers to a class from the outside.

This is super helpful when you’re working with types you can’t edit (like .NET types) or want to keep your code cleaner and more readable.

How It Works

  • Extension methods are defined as static methods inside a static class.
  • The first parameter has the this keyword in front of the type you want to extend.
  • You call the extension method like it’s a regular instance method.

Let’s say you want to add a method that capitalizes the first letter of any string

public static class StringExtensions
{
    public static string CapitalizeFirstLetter(this string str)
    {
        if (string.IsNullOrWhiteSpace(str)) return str;
        return char.ToUpper(str[0]) + str.Substring(1);
    }
}

Now you can use it like this:

string name = "satyaprakash";
string formatted = name.CapitalizeFirstLetter();
Console.WriteLine(formatted); // Output: Satyaprakash

Key Points:

FeatureExtension Method
Must be in astatic class
Declared asstatic method
First parameterUses this keyword
Can extendClasses, interfaces, even sealed types
Commonly used inLINQ, utilities, cleaner code practices

15.What is a delegate in C#?

A delegate in C# is like a function pointer or a reference to a method. It lets you store a method in a variable, and call that method later—without knowing exactly which method it is at compile time.

Think of it as a way to pass methods as parameters or swap in different methods at runtime. This is super useful for things like callbacks, event handling, and building flexible code.

Basic Syntax:

public delegate void GreetDelegate(string name);

This defines a delegate that can point to any method which takes a string and returns void.

Example:

// Step 1: Define a delegate
public delegate void GreetDelegate(string name);

// Step 2: Create a method that matches the delegate signature
public void SayHello(string name)
{
    Console.WriteLine("Hello, " + name);
}

// Step 3: Use the delegate
GreetDelegate greet = SayHello;
greet("Satyaprakash"); // Output: Hello, Satyaprakash

Key Points:

FeatureDelegate
What it doesRefers to methods
Supports parametersYes
Return valuesYes
Multicast?Yes, you can chain multiple methods

Bonus: Action & Func

C# also gives us built-in delegates:

  • Action<T> → for methods that return void
  • Func<T, TResult> → for methods that return a value

These are used all over LINQ and modern C#.


16.What is an event in C# and how is it different from a delegate?

In C#, an event is a way for a class to send notifications to other classes when something happens—like a button click, a file being downloaded, or a timer ticking.

Events are built on top of delegates, but they come with extra safety and control.

What is an Event?

An event:

  • Wraps a delegate under the hood.
  • Is used to signal that something happened.
  • Can have multiple subscribers (methods to call when the event is raised).

Example

public class Video
{
    public delegate void VideoUploadedHandler(string title);
    public event VideoUploadedHandler OnUpload;

    public void Upload(string title)
    {
        Console.WriteLine($"{title} uploaded!");
        OnUpload?.Invoke(title);  // Notify subscribers
    }
}

public class Subscriber
{
    public void Notify(string title)
    {
        Console.WriteLine($"Notification: New video uploaded - {title}");
    }
}

Usage:

var video = new Video();
var sub = new Subscriber();

video.OnUpload += sub.Notify;
video.Upload("C# Basics");  
// Output:
// C# Basics uploaded!
// Notification: New video uploaded - C# Basics

Key Differences: Event vs Delegate

FeatureDelegateEvent
What is it?A reference to a method (or methods)A wrapper around a delegate to raise signals
Can be invoked from outside?Yes No (only the declaring class can raise it)
PurposeGeneral-purpose method referenceFor publish-subscribe patterns (e.g., UI, notifications)
Can have multiple methods? Yes (multicast)Yes (subscribers)

In Simple Terms:

  • A delegate is like a call list of methods.
  • An event is a secure doorbell that other classes can ring to say, “Hey, I’m listening—tell me when something happens!”

17.What is LINQ in C#?

LINQ stands for Language Integrated Query. It’s a powerful feature in C# that lets you write queries directly in your code to filter, sort, and manipulate data—kind of like SQL, but built into the language itself.

You can use LINQ with collections like arrays, lists, XML, databases, and more. It makes working with data cleaner, shorter, and more readable.

Why Use LINQ?

Without LINQ, you’d typically write loops and conditionals to search or filter data. LINQ simplifies all that into a more declarative style—telling the compiler what you want, not how to do it.

Example:

Let’s say you have a list of numbers and want to find the even ones.

Without LINQ:

List<int> numbers = new() { 1, 2, 3, 4, 5, 6 };
List<int> evens = new();

foreach (int num in numbers)
{
    if (num % 2 == 0)
        evens.Add(num);
}

With LINQ:

var evens = numbers.Where(n => n % 2 == 0).ToList();

Cleaner, right?

Common LINQ Methods:

MethodDescription
Where()Filters data based on a condition
Select()Projects each element into a new form
OrderBy()Sorts ascending
OrderByDescending()Sorts descending
First(), FirstOrDefault()Get the first element (safely)
Count()Counts elements
Any()Checks if any match exists

Two LINQ Styles:

Method Syntax (most common):

var result = list.Where(x => x > 10).ToList();

Query Syntax (SQL-like):

var result = from x in list
             where x > 10
             select x;

Both do the same thing—it’s just a matter of style.

LINQ is like SQL for C# collections. It helps you write short, expressive, and readable code when working with data. Once you get the hang of it, you’ll wonder how you ever lived without it!


18.What is the difference between IEnumerable and IQueryable?

Both IEnumerable and IQueryable are interfaces used for querying collections, but they work differently—especially when it comes to how and where the query is executed.

Let’s break it down

IEnumerable<T>

  • Belongs to System.Collections.Generic.
  • Works in-memory (good for collections like List, Array, etc.).
  • Queries are executed on the client side (in your app).
  • Best used when you’re working with in-memory data like a list of objects already loaded

Example:

List<int> numbers = new() { 1, 2, 3, 4, 5 };
IEnumerable<int> evens = numbers.Where(n => n % 2 == 0);

Here, the filtering happens in C# code, after the list is already in memory.

IQueryable<T>

  • Belongs to System.Linq.
  • Works with remote data sources (like databases using Entity Framework).
  • Queries are executed on the server side (e.g., SQL database).
  • Translates LINQ queries into SQL commands, which improves performance for large datasets.

Example:

IQueryable<User> users = dbContext.Users.Where(u => u.IsActive);

Here, the Where clause is converted into a SQL query and runs directly in the database.

Key Differences:

FeatureIEnumerableIQueryable
NamespaceSystem.Collections.GenericSystem.Linq
Execution LocationIn-memory (client-side)Database or remote source (server-side)
Use CaseSmall/loaded collectionsLarge datasets via Entity Framework, LINQ to SQL
PerformanceLess efficient with large datasetsMore efficient due to query translation
Query TransformationNot supportedSupports expression trees (deferred execution)

In Simple Terms:

  • Use IEnumerable when you’re working with data that’s already in memory.
  • Use IQueryable when you’re querying from a database and want to let the database do the heavy lifting.

19.What are generics in C#?

Generics in C# let you write reusable, type-safe code without having to commit to a specific data type. They allow you to define classes, methods, interfaces, or delegates with a placeholder for the data type, which you specify when you use them.

Think of it like a template: you write the logic once, and it works with any type you give it.

Why Use Generics?

Without generics, you’d have to write the same code multiple times for different data types—or rely on object, which loses type safety and causes performance issues due to boxing/unboxing.

With generics, your code is:

  • Type-safe (no type casting issues)
  • Reusable
  • Performant

Example

Here’s a simple generic method:

public class Utility
{
    public void Print<T>(T value)
    {
        Console.WriteLine("Value: " + value);
    }
}

You can now use it with any type:

Utility util = new Utility();
util.Print<int>(100);            // Value: 100
util.Print<string>("Hello");     // Value: Hello

Generics in Collections

You’ve probably already used generics without realizing it:

List numbers = new List(); // Stores integers
List names = new List(); // Stores strings

Here, List<T> is a generic class where T is replaced by int, string, etc.

Benefits of Generics

FeatureBenefit
Type SafetyCatch errors at compile time
Code ReusabilityOne method/class works for any data type
PerformanceAvoids unnecessary boxing/unboxing
Cleaner CodeNo need for multiple overloaded versions

Generics let you write code that works with any type, but still keeps everything type-safe and efficient.


20.What is a nullable type in C#?

In C#, a nullable type allows a value type (like int, bool, DateTime, etc.) to hold an extra value: null.

Normally, value types can’t be null—they always have a default value. But sometimes, especially when dealing with databases or optional fields, you need a way to say, “Hey, this value might not exist.” That’s where nullable types come in.

Syntax:

To make a value type nullable, just add a ? after the type:

int? age = null;
bool? isActive = true;

Now age can either be an integer or null.

Why Use Nullable Types?

  • To represent missing or unknown values.
  • Especially helpful when working with databases, APIs, or forms where some fields may be empty.

Example:

int? score = null;

if (score.HasValue)
{
    Console.WriteLine("Score: " + score.Value);
}
else
{
    Console.WriteLine("Score not available.");
}

Or more simply:

Console.WriteLine(score ?? 0); // Prints 0 if score is null

Key Properties & Methods:

FeatureDescription
.HasValueReturns true if it’s not null
.ValueGets the value (throws error if null)
?? operatorDefines a default if value is null

Nullable vs Non-Nullable

TypeNullable Type
intint?
boolbool?
DateTimeDateTime?

Nullable types let your value types say, “I’m empty right now, and that’s okay.”


21.What is the difference between var, dynamic, and object in C#?

In C#, var, dynamic, and object all let you store different kinds of data—but they behave differently when it comes to type checking, performance, and flexibility.

Let’s break it down

varType Inference at Compile Time

  • The compiler figures out the type based on the value you assign.
  • The actual type is still known at compile time.
  • Type-safe: You can’t change its type after it’s set.

Example:

var name = "Satyaprakash";  // Treated as string
var age = 25;       

-Good for cleaner code, especially with complex types.
-You must assign a value at the time of declaration.

objectBase Type of Everything

  • Every type in C# inherits from object.
  • Used for storing any type.
  • Boxing/unboxing occurs with value types (which can hurt performance).
  • You need to cast back to the original type to access specific members.

Example:

object data = 42;
int number = (int)data;  // Requires casting

-Great for collections or APIs that need to handle multiple types.
-Not type-safe at compile time.

dynamicResolved at Runtime

  • Skips compile-time type checking.
  • Type checking is done only at runtime.
  • You can assign anything to a dynamic variable, and it won’t throw an error until you try to use it incorrectly.

Example:

dynamic value = "Hello";
Console.WriteLine(value.ToUpper());  // Works

value = 100;
// Console.WriteLine(value.ToUpper());  // Runtime error!

-Super flexible (e.g., for working with JSON, COM objects, reflection).
-Prone to runtime errors if you’re not careful.

Quick Comparison Table

Featurevarobjectdynamic
Type known atCompile timeCompile timeRuntime
FlexibilityMediumHighVery High
Type SafetyYesLimitedNo
Need casting?NoYes (for value access)❌ No
Use CaseCleaner codeGeneral-purpose storageInterop, flexible APIs

In Simple Terms:

  • var = “I know what type this is, let the compiler figure it out.”
  • object = “I want to store anything, but I’ll need to cast it later.”
  • dynamic = “I’ll figure out the type at runtime—be flexible.”

22.What is async and await in C#?

async and await are keywords in C# used to make your code asynchronous—which means it can run tasks in the background without blocking the main thread.

They’re super helpful when you’re doing slow operations like:

  • Fetching data from the internet
  • Reading/writing files
  • Accessing a database
    And you don’t want your app to freeze while it waits.

Why Use async/await?

Normally, C# code runs synchronously—line by line. If one line takes 5 seconds (like downloading a file), the whole app pauses and waits. 🕒

Using async and await, you can run that task in the background, and your app keeps responding—no freezing!

Example:

public async Task<string> GetMessageAsync()
{
    await Task.Delay(2000);  // Simulates delay (like a web request)
    return "Hello after 2 seconds!";
}

public async void ShowMessage()
{
    string msg = await GetMessageAsync();
    Console.WriteLine(msg);
}
  • async marks the method as asynchronous
  • await tells the program: “Wait here for the result, but don’t block everything else while waiting.”

Key Concepts:

TermMeaning
asyncMarks a method that contains await and runs asynchronously
awaitPauses the method until the awaited task is done
Task / Task<T>Represents an ongoing operation that may return a result
Non-blockingYour app stays responsive while waiting for the task to finish

Imagine ordering food at a restaurant.

  • Synchronous: You place your order and stand at the counter doing nothing until it’s ready.
  • Asynchronous: You place your order, then go sit down and chill. They’ll bring your food when it’s ready. You stayed productive instead of just standing there.

Bonus Tip:

You can only use await inside an async method. And if the method returns a result, use Task<T> (e.g., Task<string>). If it doesn’t, use just Task.

  • async + await = non-blocking, cleaner code for long-running tasks.
  • Makes your app feel faster and smoother—especially in UI apps and web APIs.

23.What is a task and how does it differ from a thread?

In C#, both Task and Thread are used for running code asynchronously or in parallel, but they work differently and are suited for different use cases.

What is a Thread?

A thread is the most basic unit of execution. When you create a thread, you’re literally telling the OS, “Run this code in a separate lane.”

Example:

Thread thread = new Thread(() => Console.WriteLine("Running in a thread"));
thread.Start();
  • You have full control (start, abort, etc.)
  • Uses OS-level resources
  • Can be heavier and harder to manage

What is a Task?

A Task is part of the Task Parallel Library (TPL) and represents a unit of work that can run asynchronously—like a modern, smarter wrapper around threads.

Example:

Task.Run(() => Console.WriteLine("Running in a task"));
  • Easier to work with
  • Integrates with async/await
  • More efficient with thread-pooling and scheduling

Key Differences:

FeatureThreadTask
LevelLow-level (direct thread)High-level (abstraction over threads)
ControlManual (start/abort/suspend)Managed by .NET runtime
PerformanceHeavier, more overheadLightweight, uses thread pool
Exception HandlingManualEasier with built-in support
Suitable ForLong-running, dedicated workShort background work, async code
Supports await?NoYes

When to Use What?

  • Use Task when you’re doing background work like I/O operations, network calls, or parallel processing.
  • Use Thread only when you need low-level control, like setting thread priority or creating long-running, dedicated threads.

Real-Life Analogy:

  • A Thread is like hiring a new employee just for one job. You pay overhead, give them a desk, etc.
  • A Task is like giving an assignment to someone in your internal team who’s already available. Faster and more efficient.

In Simple Words:

  • Thread = lower-level, manual, more control but more work.
  • Task = higher-level, smarter, and works beautifully with modern async/await.

24.How is exception handling done in C#?

In C#, exception handling is done using the try, catch, finally, and throw keywords. It’s a way to handle unexpected errors in your code gracefully—without crashing the application.

What is an Exception?

An exception is an error that happens at runtime, like:

  • Dividing by zero
  • Trying to open a file that doesn’t exist
  • Accessing a null object

Rather than letting the app crash, C# lets you catch and handle the problem.

Basic Syntax:

try
{
    // Code that might throw an exception
    int result = 10 / 0;
}
catch (DivideByZeroException ex)
{
    // Handle the error
    Console.WriteLine("You can't divide by zero.");
}
finally
{
    // Optional: Runs no matter what
    Console.WriteLine("Cleaning up...");
}

Keywords Explained:

KeywordPurpose
tryWraps code that might cause an exception
catchCatches and handles specific or general exceptions
finallyOptional block that runs always (cleanup, closing files etc.)
throwManually raise an exception or re-throw one

Example with Multiple Catch Blocks:

try
{
    string text = null;
    Console.WriteLine(text.Length);
}
catch (NullReferenceException)
{
    Console.WriteLine("Oops! You're trying to access something that's null.");
}
catch (Exception ex)
{
    Console.WriteLine("Something went wrong: " + ex.Message);
}
finally
{
    Console.WriteLine("This will run no matter what.");
}

Best Practices

  • Catch specific exceptions first (NullReferenceException, FileNotFoundException, etc.).
  • Use finally for cleanup (closing connections, releasing resources).
  • Don’t swallow exceptions silently—log them!
  • Avoid catching Exception unless you have a good reason.

In Simple Words:

Exception handling is like putting a safety net around your code.
If something goes wrong, you catch it, handle it gracefully, and keep the app running smoothly.

25.What is the using statement in C#?

The using statement in C# is a shortcut for managing resources—like files, database connections, streams, etc.—that need to be cleaned up properly when you’re done using them.

Instead of manually calling .Dispose() to free up resources, the using statement does it for you automatically. It’s super helpful for avoiding memory leaks or keeping things tidy in your code.

Basic Syntax:

using (FileStream fs = new FileStream(“file.txt”, FileMode.Open))
{
// Use the file stream here
// It will be automatically closed and disposed after this block
}

Behind the scenes, C# ensures that the Dispose() method is called on the FileStream—even if an exception occurs. So it’s safe and clean.

When Should You Use It?

Use using with any class that implements IDisposable, such as:

  • FileStream
  • StreamReader / StreamWriter
  • SqlConnection
  • HttpClient (in short-lived use cases)
  • Any custom class that implements cleanup logic

Old vs New Syntax (C# 8+):

Old way (pre-C# 8):

using (var reader = new StreamReader("file.txt"))
{
    var content = reader.ReadToEnd();
}

New way (C# 8+ with using declaration):

using var reader = new StreamReader("file.txt");
var content = reader.ReadToEnd();

Why It’s Useful:

  • Automatic cleanup of unmanaged resources
  • Cleaner code with fewer chances to forget .Dispose()
  • Helps avoid resource leaks (e.g., too many open files)

Think of using as telling C#:
“Hey, I’m going to use this resource temporarily—please clean it up for me when I’m done.”


26.What is a static class in C#?

A static class in C# is a class that can’t be instantiated (you can’t create an object of it), and it can only contain static members—like static methods, properties, or fields.

It’s usually used when you want to group utility or helper methods that don’t need to maintain any state (data).

Example:

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

Usage:

int result = MathHelper.Add(5, 10); // No need to create an object

Key Characteristics of a Static Class:

FeatureDescription
Cannot be instantiatedYou can’t do new MathHelper()
All members must be staticNo instance members allowed
Sealed automaticallyCan’t be inherited
Memory-efficientNo object overhead

When to Use a Static Class?

  • Utility/helper functions (e.g., Math, File, DateTime)
  • Extensions methods (they must be inside a static class)
  • When you don’t need to store or manage object state

What You Can’t Do:

MathHelper helper = new MathHelper(); // ❌ Error! Can’t create object of static class

Summary:

  • A static class = a container of static stuff.
  • You use it without creating objects.
  • Perfect for utility-style methods and logic.

Think of a static class like a toolbox. You don’t create a new toolbox every time you want to use a screwdriver—you just open it and grab what you need.


27.What are indexers in C#?

Indexers in C# let you access objects like arrays using square brackets [], even if the object isn’t actually an array. They allow you to treat your own class or struct like a collection, by defining how it should behave when accessed with an index.

So instead of calling methods like GetItem(0), you can just do myObject[0]. Neat, right?

Example:

public class Week
{
    private string[] days = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

    public string this[int index]
    {
        get { return days[index]; }
        set { days[index] = value; }
    }
}

Usage:

Week week = new Week();
Console.WriteLine(week[1]); // Output: Mon
week[1] = "Monday";
Console.WriteLine(week[1]); // Output: Monday

Key Points:

  • Defined using this keyword with a parameter (usually an int).
  • Can be read-only, write-only, or read/write.
  • Can be overloaded (e.g., by index type: int, string, etc.).
  • Great for wrapping custom data structures or managing internal collections.

Syntax Breakdown:

public return_type this[parameter]
{
get { // return value }
set { // assign value }
}

You can have multiple indexers with different parameter types:

public string this[string dayName] { get; set; }

Imagine a bookshelf with numbered slots. An indexer lets you say shelf[2] to get the 3rd book, instead of calling GetBookAt(2).

Summary:

  • Indexers make custom objects feel like arrays or lists.
  • Clean, intuitive syntax for data access.
  • Great for creating wrapper classes or simplifying data access.

28.What is a partial class in C#?

A partial class in C# lets you split a single class into multiple files. At compile time, all the parts are combined into one single class. This feature is super handy when a class is too big or auto-generated, and you want to organize or extend it cleanly without touching the original code.

Simple Example:

File 1: Person.Part1.cs

public partial class Person
{
    public string FirstName { get; set; }
}

File 2: Person.Part2.cs

public partial class Person
{
    public string LastName { get; set; }

    public void SayHello()
    {
        Console.WriteLine($"Hi, I'm {FirstName} {LastName}");
    }
}

Both files together make one full Person class.

When is it useful?

  • When using auto-generated code (like from a designer or ORM).
  • To organize large classes into logical pieces.
  • To allow team collaboration, where different developers work on different parts of the same class.

Key Rules:

RuleDescription
Must use partial keywordEvery part must include partial
Must be in the same namespaceAnd the same assembly
Can span multiple filesAll get combined during compilation
Can include methods, properties, events, etc.All typical class members allowed

Summary:

  • A partial class = a class split across files.
  • Helps with organization, extensibility, and collaboration.
  • The compiler merges them into a single class behind the scenes.

29.What are tuples in C#?

A tuple in C# is a lightweight way to group multiple values into a single object without creating a custom class or struct. You can think of it as a quick container to return or pass around a few values together.

It’s perfect when you need to return more than one value from a method but don’t want the overhead of defining a new type.

Example:

(string name, int age) GetPerson()
{
return (“Satyaprakash”, 28);
}

Usage:

var person = GetPerson();
Console.WriteLine($"Name: {person.name}, Age: {person.age}");

Easy, readable, and doesn’t need a custom Person class.

Ways to Create Tuples:

Using ValueTuple (C# 7 and later):

var book = ("C# in Depth", 2024);
Console.WriteLine(book.Item1); // C# in Depth

With named elements:

var employee = (name: "Alice", salary: 50000);
Console.WriteLine(employee.name); // Alice

Using Tuple.Create (older style):

var tuple = Tuple.Create("Pen", 20);
Console.WriteLine(tuple.Item1); // Pen

Tip: The newer ValueTuple is more flexible and readable with named elements.

Common Use Cases:

  • Returning multiple values from a method
  • Quickly bundling data together for temporary use
  • Avoiding boilerplate when structs/classes feel too heavy

Imagine going to a grocery store and carrying a banana, a milk carton, and a chocolate bar in your hands—not a fancy bag. That little bunch you’re holding? That’s your tuple—a quick, temporary bundle.

Summary:

  • Tuples let you group multiple values without defining a new type.
  • You can access items with names (tuple.name) or by position (Item1, Item2).
  • Great for quick returns, temporary groups, and cleaner code.

30.What is pattern matching in C#?

Pattern matching in C# lets you check a value’s type and extract data from it in a simpler and more readable way. Instead of using traditional if and cast statements, you can use patterns directly in conditions to write cleaner code.

It’s like saying: “If this thing looks like a certain shape or type, grab it and do something with it.”

Basic Example:

object obj = "Hello, C#";

if (obj is string message)
{
    Console.WriteLine(message.ToUpper());
}

This checks if obj is a string, and if so, assigns it to message.

Types of Pattern Matching in C#:

Pattern TypeDescription
Type PatternChecks and casts in one go (is string msg)
Constant PatternCompares with a constant value (case 0:)
Relational PatternCompares using <, >, <=, etc.
Logical PatternCombines patterns with and, or, not
Property PatternChecks object properties ({ Name: "John" })
Positional PatternDeconstructs tuples or records

Switch with Pattern Matching:

object shape = new Circle(5);

switch (shape)
{
    case Circle c:
        Console.WriteLine($"Circle with radius {c.Radius}");
        break;
    case Square s:
        Console.WriteLine($"Square with side {s.Side}");
        break;
}

This is much better than using multiple if checks and casts.

Property Pattern Example:

Person p = new Person { Name = “Alice”, Age = 30 };

if (p is { Age: > 18 })
{
Console.WriteLine("Adult");
}

Checks the Age property directly inside the condition!

Why Use Pattern Matching?

  • Less code, more clarity
  • Avoids manual casting
  • Makes complex conditionals more expressive
  • Works great with switch expressions, records, and tuples

31.What is the difference between String, StringBuilder, and string?

In C#, String, string, and StringBuilder are all used for working with text—but they each serve a different purpose depending on how you’re working with that text.

string vs String

  • string is just an alias (a shortcut) for System.String.
  • So this:
string name = "Satyaprakash";

Is exactly the same as this:

String name = "Satyaprakash";

Use string for variable declarations (it’s more common), and use String when calling static methods like String.IsNullOrEmpty().

Why String is immutable:

Strings in C# are immutable, which means once you create a string, you can’t change it. Every time you “modify” a string, you’re actually creating a new one in memory.

Example:

string text = "Hello";
text += " World";  // This creates a *new* string behind the scenes

If you’re doing lots of string manipulations (like in loops), this can become slow and memory-heavy.

StringBuilder

StringBuilder is part of System.Text and is made for scenarios where you’re changing the content a lot—like in loops or building long strings dynamically.

StringBuilder sb = new StringBuilder();
sb.Append("Hello");
sb.Append(" World");
Console.WriteLine(sb.ToString());  // Output: Hello World

Faster and more memory-efficient for repeated modifications.

When to Use What?

  • Use string for regular text storage, comparisons, formatting, etc.
  • Use StringBuilder when you’re building a large string dynamically or modifying it a lot.

In Simple Words:

  • string is like a whiteboard you can’t erase—you write something new every time.
  • StringBuilder is like a notebook where you can keep adding notes on the same page.

32.What are the different types of collections in C#?

Collections in C# are like containers that hold groups of objects. Instead of creating multiple variables, you can use a collection to store, manage, and work with data more efficiently.

C# offers different types of collections based on your needs—like dynamic sizing, key-value pairs, or thread safety.

Categories of Collections in C#:

1. Non-Generic Collections (older, less type-safe – in System.Collections)

  • ArrayList – Resizable array that holds objects.
  • Hashtable – Key-value pairs (like a dictionary, but untyped).
  • Stack – Last In, First Out (LIFO) collection.
  • Queue – First In, First Out (FIFO) collection.

These can store any type, but you lose type safety and may need to cast.

2. Generic Collections (modern, type-safe – in System.Collections.Generic)

  • List<T> – A resizable array of a specific type.
  • Dictionary<TKey, TValue> – Stores key-value pairs with type safety.
  • HashSet<T> – Stores unique values only (no duplicates).
  • SortedList<TKey, TValue> – A sorted dictionary-like structure.
  • Queue<T> – Generic FIFO.
  • Stack<T> – Generic LIFO.
  • LinkedList<T> – A doubly linked list.

These are type-safe and more performant.

3. Concurrent Collections (thread-safe – in System.Collections.Concurrent)

  • ConcurrentDictionary<TKey, TValue>
  • ConcurrentQueue<T>
  • ConcurrentStack<T>
  • BlockingCollection<T>

Use these in multi-threaded or parallel processing environments.

4. Observable Collections (in System.Collections.ObjectModel)

  • ObservableCollection<T> – Notifies UI or listeners when items are added, removed, or changed. Useful in WPF, MVVM, and data-binding.

In Simple Words:

  • Use generic collections (List<T>, Dictionary<TKey, TValue>) for most apps.
  • Use concurrent collections when multiple threads are involved.
  • Use observable collections for UI binding.
  • Avoid non-generic ones unless you’re maintaining legacy code.

33.What is the difference between Array and ArrayList?

Both Array and ArrayList are used to store multiple values in C#. But they work differently when it comes to type safety, flexibility, and performance.

1. Type Safety

  • Array is strongly typed – it can only store a fixed type.
int[] numbers = new int[] { 1, 2, 3 };  // Only integers allowed
  • ArrayList is not type-safe – it stores items as object, so it can hold any type, but you need to cast when retrieving values.
ArrayList list = new ArrayList();
list.Add(1);
list.Add("Hello");  // Allowed, but risky

2.Performance

  • Array is faster because it avoids boxing/unboxing and type casting.
  • ArrayList may involve performance hits due to boxing (for value types like int, bool, etc.) and type conversion.

3. Resizing

  • Array has a fixed size once declared.
int[] nums = new int[3];
// Can't add more than 3 elements unless you create a new array
  • ArrayList is dynamic – it grows as you add more items.
ArrayList list = new ArrayList();
list.Add(10);  // Add as many as you want

4. Boxing and Unboxing

  • Happens in ArrayList when working with value types:
list.Add(10);              // Boxing (int -> object)
int num = (int)list[0];    // Unboxing
  • No boxing/unboxing with arrays of value types:
int num = numbers[0];  // Direct access

Bonus Tip:

For modern C# development, avoid ArrayList and use List<T> instead. It gives you the best of both worlds: type safety and dynamic sizing.

List<int> numbers = new List<int>();
numbers.Add(10);  // Type-safe and resizable

34.What are anonymous types in C#?

Anonymous types in C# let you create objects on the fly without defining a class or a structure. You just declare inline properties with values, and C# automatically creates the type for you behind the scenes.

Real-World Analogy:

Imagine you just want to store a name and age quickly—without creating a full class like this:

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

With anonymous types, you can skip that boilerplate and write:

var person = new { Name = "Alice", Age = 25 };
Console.WriteLine(person.Name);  // Output: Alice

Behind the scenes, the compiler creates a class for you with Name and Age properties.

Key Points:

  • Created using the new { ... } syntax.
  • Properties are read-only (you can’t modify them after creation).
  • Mostly used when you don’t need a reusable class—just a quick data structure.
  • Often used with LINQ queries to project custom shapes.

Example in LINQ:

var users = new[] {
    new { FirstName = "John", LastName = "Doe" },
    new { FirstName = "Jane", LastName = "Smith" }
};

foreach (var user in users)
{
    Console.WriteLine($"{user.FirstName} {user.LastName}");
}

Or with LINQ:

var result = students.Select(s => new { s.Name, s.Grade });

In Simple Words:

Anonymous types let you build small, temporary objects without writing a full class. They’re perfect for quick jobs like data shaping in LINQ, but not meant for long-term use.


35.What are the different types of inheritance in C#?

Inheritance in C# lets one class reuse or extend the functionality of another class. It’s like saying, “this new class is a specialized version of that existing class.”

C# supports different types of inheritance—even though it doesn’t allow multiple inheritance directly (like C++), it has smart alternatives.

Types of Inheritance in C#:

1. Single Inheritance

A class inherits from one base class.

class Animal
{
    public void Speak() => Console.WriteLine("Animal sound");
}

class Dog : Animal
{
    public void Bark() => Console.WriteLine("Bark");
}

Dog inherits from Animal.

2. Multilevel Inheritance

A class inherits from a derived class which itself inherits from another class.

class Animal { }
class Mammal : Animal { }
class Dog : Mammal { }

This creates a chain of inheritance.

3. Hierarchical Inheritance

Multiple classes inherit from the same base class.

class Animal { }
class Dog : Animal { }
class Cat : Animal { }

Dog and Cat both get properties and methods from Animal.

Multiple Inheritance (via Interfaces)

C# doesn’t support multiple class inheritance directly, but it does support it via interfaces.

interface IWalk
{
    void Walk();
}

interface IRun
{
    void Run();
}

class Person : IWalk, IRun
{
    public void Walk() => Console.WriteLine("Walking");
    public void Run() => Console.WriteLine("Running");
}

This gives you the benefit of multiple behaviors without the messiness of multiple class hierarchies.

Why Not Multiple Class Inheritance?

To avoid problems like the Diamond Problem, C# uses interfaces to provide flexibility while keeping the design clean and unambiguous.

n Simple Words:

  • C# supports clean, safe inheritance.
  • Use interfaces when you need multiple behaviors.
  • Inheritance helps with code reusability, but overusing it can make designs complex—use wisely.

36.What is encapsulation in C#?

Encapsulation in C# is the concept of hiding the internal details of an object and exposing only what’s necessary to the outside world. It’s like putting your complex logic inside a secure box and letting others interact with it only through safe, well-defined doors (called methods or properties).

Real-World Analogy:

Think of a coffee machine.
You just press a button to get coffee—you don’t need to know how it works inside (grinding beans, heating water, etc.). That’s encapsulation!

How Encapsulation Works in C#:

It’s achieved using:

  • Private fields (to hide data)
  • Public properties/methods (to safely access or modify that data)
public class BankAccount
{
    private double balance; // private = hidden

    public void Deposit(double amount)
    {
        if (amount > 0)
            balance += amount;
    }

    public double GetBalance()
    {
        return balance;
    }
}

Here, balance is encapsulated — the user can’t directly change it, only through methods like Deposit().

Why Encapsulation is Important:

  • Protects data from unwanted access or modification
  • Makes code easier to maintain
  • Allows you to change implementation without affecting users
  • Supports the “black box” idea — users don’t need to know how it works inside

In Short:

  • Encapsulation =
  • Hide the internal state
  • Expose only what’s needed
  • Control access through methods and properties

37.What is polymorphism and its types?

Polymorphism in C# means “many forms.” It allows the same method or behavior to act differently based on the context. In other words, it lets you use a single interface or method name to represent different underlying behaviors.

Real-Life Analogy:

Imagine you have a method called Draw().
If you call it on a Circle, it draws a circle.
If you call it on a Rectangle, it draws a rectangle.
Same method name — different behavior based on the object.

Types of Polymorphism in C#:

1. Compile-time Polymorphism (a.k.a. Static Polymorphism)

  • Achieved through method overloading or operator overloading
  • Decision made at compile time

Example – Method Overloading:

public class Calculator
{
    public int Add(int a, int b) => a + b;
    public double Add(double a, double b) => a + b;
}

Same method name Add, but different parameter types — that’s compile-time polymorphism.

2. Run-time Polymorphism (a.k.a. Dynamic Polymorphism)

  • Achieved through method overriding using inheritance and the virtual/override keywords
  • Decision made at runtime

Example – Method Overriding:

public class Animal
{
    public virtual void Speak() => Console.WriteLine("Animal sound");
}

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

Animal myDog = new Dog();
myDog.Speak(); // Output: Bark

Even though the type is Animal, it runs the Dog version of Speak() — that’s runtime polymorphism.

In Simple Words:

Polymorphism means writing clean, flexible, and reusable code where the same method behaves differently depending on the object. It’s one of the key pillars of Object-Oriented Programming (OOP) in C#.


38.What is dependency injection in C#?

Dependency Injection (DI) is a design pattern in C# that helps loosen the coupling between classes. Instead of a class creating its own dependencies, those dependencies are provided (injected) from the outside.

Think of it as someone handing you the tools you need, instead of you going to buy them yourself.

Real-Life Analogy:

Imagine a car that needs an engine to run.

Without DI:

class Car
{
    Engine engine = new Engine(); // tightly coupled
}

With DI:

class Car
{
    private Engine _engine;
    public Car(Engine engine)  // engine is injected from outside
    {
        _engine = engine;
    }
}

Now, you can easily replace or mock the Engine without changing the Car class.

Why Use Dependency Injection?

  • Reduces tight coupling between classes
  • Makes code easier to test and maintain
  • Encourages reusability
  • Promotes the Single Responsibility Principle (SRP)

Types of Dependency Injection in C#:

  • Constructor Injection (most common):
public class OrderService
{
    private readonly IEmailService _emailService;

    public OrderService(IEmailService emailService)
    {
        _emailService = emailService;
    }

    public void PlaceOrder()
    {
        // do something
        _emailService.SendEmail();
    }
}
  • Property Injection
public class OrderService
{
    public IEmailService EmailService { get; set; }

    public void PlaceOrder()
    {
        EmailService.SendEmail();
    }
}
  • Method Injection
public void Notify(IEmailService emailService)
{
    emailService.SendEmail();
}

In Simple Words:

Dependency Injection is like saying:
“Don’t build the tools yourself—just ask for them, and someone else (the DI container) will hand them to you.”

It makes your code cleaner, testable, and easier to change.


39.What is the purpose of the nameof operator?

The nameof operator in C# is used to get the name of a variable, property, class, method, or member as a string, without hardcoding it.

Instead of writing "Username" as a string, which could lead to bugs if the variable name changes, you use nameof(Username) — and the compiler keeps it in sync!

Why Use nameof?

  • Helps avoid magic strings
  • Makes your code refactor-safe
  • Improves readability and maintainability
  • Commonly used in logging, validation, exceptions, and UI bindings

Example:

public class User
{
    public string Username { get; set; }
}

void PrintError()
{
    Console.WriteLine(nameof(User.Username)); // Output: Username
}

Now, if you ever rename Username to UserName, nameof will automatically reflect that change.

In Simple Words:

The nameof operator is like asking the compiler:
“What is the name of this variable or method?”
It gives you the name as a string, but safely and reliably, without the risks of typos.


40.What is garbage collection in C#?

Garbage Collection (GC) in C# is an automatic memory management feature. It keeps your app clean by automatically finding and freeing up memory that’s no longer being used—so you don’t have to do it manually.

Think of it as a clean-up crew that runs in the background, collecting and disposing of objects your code no longer needs.

Why is it Important?

Without garbage collection, you’d have to manually manage memory (like in C or C++), which is error-prone and can cause memory leaks or crashes. GC saves you from all that hassle.

How Garbage Collection Works:

  1. You create objects using new.
  2. As long as you’re using the object, it stays in memory.
  3. When no part of your code references that object anymore, it becomes eligible for garbage collection.
  4. The Garbage Collector (GC) steps in and frees the memory.

You don’t decide when GC runs—the .NET runtime does it automatically, based on memory pressure.

Example:

public void CreateUser()
{
    User user = new User();  // Memory allocated
    // do something with user
}   // After this method ends, `user` is out of scope and can be collected

Benefits of Garbage Collection:

  • No need to manually free memory
  • Avoids memory leaks and crashes
  • Keeps your app running efficiently

Can You Force GC?

Yes, but you usually shouldn’t. You can call:

GC.Collect();

But it’s not recommended unless you absolutely know what you’re doing—manual GC can actually hurt performance.

In Simple Words:

Garbage Collection in C# is like having a smart janitor who automatically cleans up unused stuff in your app’s memory so you can focus on writing code, not managing memory.


41.What are finalizers and destructors in C#?

In C#, finalizers (also known as destructors) are special methods that are automatically called when an object is being destroyed by the garbage collector. They’re typically used to clean up unmanaged resources like file handles, database connections, or network sockets—things the garbage collector doesn’t know how to handle on its own.

Basic Syntax (Destructor):

class MyClass
{
    ~MyClass()
    {
        // cleanup code here
        Console.WriteLine("Destructor called!");
    }
}
  • It looks like a constructor but with a tilde ~ in front.
  • You cannot call it manually — the GC calls it when the object is being collected.
  • You can’t define parameters in a destructor.

Key Points:

  • Only one destructor is allowed per class.
  • It’s automatically called by the garbage collector, not by the programmer.
  • Destructors are non-deterministic – you don’t know exactly when they’ll be called.
  • Mostly used when you’re dealing with unmanaged resources (rare in modern C#).

Example Scenario:

If you open a file and forget to close it, a destructor can be used as a safety net:

class FileHandler
{
    private FileStream _file;

    public FileHandler(string filePath)
    {
        _file = new FileStream(filePath, FileMode.Open);
    }

    ~FileHandler()
    {
        _file.Close(); // Ensure file is closed
        Console.WriteLine("File closed by destructor.");
    }
}

But Wait — Prefer IDisposable Instead!

In most real-world apps, we use the IDisposable interface and the using statement instead of destructors, because:

  • IDisposable.Dispose() is called manually, so it’s deterministic.
  • Destructors introduce GC overhead and delay cleanup.
using (var file = new StreamReader("data.txt"))
{
// File is automatically closed after this block
}

In Simple Words:

Finalizers/Destructors are like an emergency cleanup crew — they step in if you forget to close something important. But in modern C# apps, you should prefer Dispose() and using for cleaner, faster, and more predictable resource management.


42.What is the difference between Dispose() and Finalize()?

Both Dispose() and Finalize() are used to release resources, but they work differently and serve different purposes. Here’s how they compare:

Dispose()Manual Cleanup

  • Comes from the IDisposable interface
  • Called explicitly by the developer
  • Used to release both managed and unmanaged resources
  • Deterministic — you decide when it happens
public class MyResource : IDisposable
{
    public void Dispose()
    {
        // Clean up here
        Console.WriteLine("Dispose called");
    }
}

You usually use it like this:

using (var res = new MyResource())
{
    // use the resource
} // Dispose is automatically called at the end of this block

Finalize() (aka Destructor) – Automatic Cleanup

  • Called by the garbage collector (GC)
  • Used only for unmanaged resources
  • Non-deterministic — you don’t know when it will be called
  • Defined using a destructor (~ClassName)
~MyResource()
{
    // Cleanup logic
    Console.WriteLine("Finalize called");
}

You can’t control when this runs — the GC decides based on memory pressure.

est Practice: Combine Both Using the Dispose Pattern

If you’re handling unmanaged resources, the best approach is to use both:

public class MyClass : IDisposable
{
    ~MyClass() => Dispose(false);

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // Tell GC: no need to call Finalize
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // free managed resources
        }

        // free unmanaged resources
    }
}

In Simple Words:

  • Use Dispose() when you want to be in charge of cleanup.
  • Use Finalize() when you forget to clean up, and GC steps in as a backup.
  • But honestly — always prefer Dispose() and the using statement for better control and performance.

43.What are attributes in C#?

In C#, attributes are like little tags or labels that you attach to code (classes, methods, properties, etc.) to provide additional information about them. This metadata can then be read at runtime using reflection.

Think of it like this:

If your code is a book, attributes are sticky notes with instructions or comments — they don’t change the content, but they help tools and frameworks understand how to treat that content.

Real Example:

[Obsolete("Use NewMethod instead")]
public void OldMethod()
{
Console.WriteLine("This method is outdated.");
}

Here, the [Obsolete] attribute tells developers not to use OldMethod. If they do, they’ll get a warning in the editor or during compilation.

Commonly Used Attributes in C#:

AttributePurpose
[Obsolete]Marks code as outdated or deprecated
[Serializable]Marks a class as serializable
[DllImport]Used for calling unmanaged code
[Required]Used in ASP.NET for model validation
[Key]Marks a primary key in Entity Framework
[HttpGet] / [HttpPost]Used in ASP.NET Core for routing actions

In Simple Words:

Attributes are like invisible instructions attached to your code that can influence behavior at runtime, compile time, or during code analysis — without changing the actual logic.


44.What is reflection in C#?

Reflection in C# is a powerful feature that allows you to inspect, explore, and even manipulate the metadata and behavior of objects, types, and assemblies — at runtime.

In simple words: Reflection lets your code examine other code, just like opening a toolbox and seeing what’s inside a class or method while the program is running.

What Can You Do with Reflection?

  • Find out the type of an object
  • List all properties, methods, fields, and constructors of a class
  • Invoke methods or access properties dynamically
  • Create instances of types at runtime
  • Read custom attributes

Example:

Type type = typeof(string);
Console.WriteLine("Class Name: " + type.Name);

foreach (var method in type.GetMethods())
{
    Console.WriteLine("Method: " + method.Name);
}

This prints out all methods of the string class — while your app is running.

Real-World Uses of Reflection:

  • ORMs like Entity Framework use it to map database tables to classes.
  • ASP.NET uses it to map routes to controller actions.
  • Serialization libraries use it to convert objects to/from JSON or XML.
  • Plugins and dependency injection systems use it to discover and load types dynamically.

Things to Keep in Mind:

  • Reflection is slower than normal code, so don’t overuse it.
  • It can bypass access modifiers (e.g., access private members), so it must be used responsibly.
  • Great for frameworks and libraries, but use it only when truly needed.

In Simple Words:

Reflection is like giving your C# program X-ray vision — it can look inside other objects, find out what they’re made of, and even interact with them on the fly.


45.What is the lock statement used for in multithreading?

In C#, the lock statement is used to prevent multiple threads from accessing the same block of code or resource at the same time.

Think of it like putting a “Do Not Disturb” sign on a shared resource — only one thread is allowed in at a time, and others have to wait their turn. This helps avoid data corruption, race conditions, and other weird bugs that can happen when threads clash.

Why Use lock?

In multithreaded apps, two or more threads might try to update the same variable or access the same file or object at once. Without protection, this can lead to unexpected behavior.

The lock statement ensures that only one thread can execute a critical section of code at a time.

Example:

class Counter
{
    private int _count = 0;
    private readonly object _lockObj = new object();

    public void Increment()
    {
        lock (_lockObj)
        {
            _count++;
        }
    }
}

Here, even if 10 threads call Increment() at once, only one thread at a time will be able to enter the lock block and update _count.

Key Points:

  • lock takes an object as a token (often a private readonly object).
  • Only one thread can hold the lock on that object at any given time.
  • Other threads wait (block) until the lock is released.

Don’ts:

  • Never lock on this, typeof(...), or any public object — it can cause deadlocks or conflicts with external code.
  • Keep the locked section as short as possible to avoid slowing down your app.

In Simple Words:

The lock statement in C# makes sure only one thread can access a sensitive piece of code at a time. It’s like using a bathroom key at a gas station — only one person gets to use it, others wait patiently until it’s free!


46.What is thread safety and how do you achieve it?

Thread safety means making sure that multiple threads can access and use shared data or code without causing conflicts or bugs.

In simpler terms: imagine two people trying to write on the same whiteboard at the same time — unless there’s some coordination, they’ll overwrite each other’s work. Thread safety is that coordination.

Why Thread Safety Matters

In multithreaded apps (like background workers, async tasks, or parallel operations), multiple threads might try to:

  • Read or write the same variable
  • Modify the same object
  • Call the same method at the same time

If you’re not careful, this can lead to data corruption, unexpected behavior, or crashes.

How to Achieve Thread Safety in C#

Here are the most common ways:

1. lock Statement

Prevents multiple threads from executing a block of code at the same time.

private readonly object _lock = new object();

public void SafeIncrement()
{
    lock (_lock)
    {
        // Only one thread at a time
        _count++;
    }
}

2. Monitor, Mutex, Semaphore

Advanced synchronization tools when you need more control than lock.

Monitor.Enter(_lock);
try
{
    // critical section
}
finally
{
    Monitor.Exit(_lock);
}

3. Interlocked Class

For simple operations like incrementing a number, this is super fast and safe.

Interlocked.Increment(ref _count);

4. Concurrent Collections

Use thread-safe collections like:

  • ConcurrentDictionary<TKey, TValue>
  • ConcurrentQueue<T>
  • ConcurrentBag<T>

These handle all the locking and safety internally.

5. Immutability

Design your objects so their state can’t change after creation. Immutable objects are naturally thread-safe.

6. Avoid Shared State

Sometimes the best solution is to avoid shared variables altogether. Let each thread have its own copy of data.

What Happens If It’s Not Thread Safe?

You could see:

  • Inconsistent data (e.g., wrong total or count)
  • Crashes or hangs
  • Bugs that are hard to reproduce (and fix!)

In Simple Words:

Thread safety is making sure that multiple threads don’t mess things up when working together. You can achieve it using tools like lock, thread-safe collections, and smart design.


47.What is the difference between Task.WhenAll and Task.WhenAny?

Both Task.WhenAll and Task.WhenAny are used when you’re dealing with multiple asynchronous tasks in C#. The difference lies in when they complete and what they return.

Task.WhenAll – Waits for All Tasks to Finish

Task.WhenAll will wait until every task you give it is complete. It’s useful when you need all results or need to ensure that all operations have finished before moving on.

await Task.WhenAll(task1, task2, task3);

Use this when:

  • You want to run tasks in parallel
  • But wait until all of them are done

Task.WhenAny – Waits for Any One Task to Finish

Task.WhenAny completes as soon as the first task finishes, regardless of whether the others are still running. It returns the first completed task.

Task finishedFirst = await Task.WhenAny(task1, task2, task3);

Use this when:

  • You’re interested in the first completed task
  • Maybe you want to use the result of whichever finishes first and cancel the rest

Example:

var task1 = Task.Delay(2000); // finishes in 2 seconds
var task2 = Task.Delay(1000); // finishes in 1 second

await Task.WhenAll(task1, task2); // waits ~2 seconds
await Task.WhenAny(task1, task2); // waits ~1 second

Key Differences Recap:

FeatureTask.WhenAllTask.WhenAny
CompletionWhen all tasks complete ✅When any one task completes ✅
Return valueTask that completes with all resultsTask<Task> (the first to finish)
Common use caseParallel processing with full resultsTime-sensitive or fallback scenarios

In Simple Words:

  • Task.WhenAll = “Wait for everyone to finish.”
  • Task.WhenAny = “Let me know as soon as anyone is done.”

48.What are records in C# 9.0?

Records in C# 9.0 are a special kind of class designed to make it easier to create immutable, data-centric objects — kind of like lightweight containers for data, with a lot of built-in goodies.

They were introduced to reduce boilerplate code when working with objects that primarily store data (like models or DTOs).

Think of records as:

Immutable by default
Support value-based equality
Have built-in ToString(), Equals(), and GetHashCode()
Use concise syntax

Basic Example:

public record Person(string Name, int Age);

This single line gives you:

  • A constructor
  • Read-only properties (Name, Age)
  • Equality logic (Equals, ==)
  • ToString() that prints: Person { Name = John, Age = 30 }

Compare that to writing a full class with all that manually!

In Simple Words:

Records are like smarter, cleaner classes that are made for holding data, with less code and more power. They’re great for APIs, models, messages, or anywhere you’re just packaging data.


49.What is the init accessor in C#?

The init accessor is a feature introduced in C# 9.0 that allows you to set properties only during object creation — just like set, but only once.

It’s a great way to make your objects immutable after they’re created, while still allowing a clean and readable initialization syntax.

In Simple Words:

  • set = can change the property anytime
  • init = can set the property only when creating the object

Example:

public class User
{
    public string Name { get; init; }
    public int Age { get; init; }
}

Now you can do this:

var user = new User { Name = "Alice", Age = 25 };

But this will cause a compile-time error:

user.Age = 30; //  Not allowed

Why Use init?

  • It lets you write immutable classes while still supporting object initializer syntax
  • Safer than set — you can’t accidentally modify the object later
  • Cleaner than needing constructors with lots of parameters

In Simple Terms:

init is like a “one-time setter” — you can assign a value while creating the object, but you can’t change it afterward.

It gives you the best of both worlds: Immutability + Clean initialization


50.How do you create a custom exception in C#?

In C#, you can create your own custom exception by inheriting from the Exception class (or one of its subclasses). This is helpful when you want to throw an error that’s specific to your application’s logic — something more meaningful than just “Exception.”

Basic Example:

public class InvalidAgeException : Exception
{
    public InvalidAgeException() { }

    public InvalidAgeException(string message)
        : base(message) { }

    public InvalidAgeException(string message, Exception inner)
        : base(message, inner) { }
}

Now you can use it like this:

int age = -5;

if (age < 0)
{
    throw new InvalidAgeException("Age cannot be negative.");
}

Constructor Breakdown:

  • public InvalidAgeException() – default constructor
  • public InvalidAgeException(string message) – lets you pass a custom error message
  • public InvalidAgeException(string message, Exception inner) – useful for exception chaining, where one exception causes another

Why Create Custom Exceptions?

  • Makes your errors more descriptive
  • Helps with better debugging
  • Allows specific exception handling (catching your exception separately)

In Simple Words:

Creating a custom exception in C# is just like making your own “error type” with a name and message that actually make sense for your application. It’s like labeling your problems clearly so you (and other developers) can solve them faster.

51.What are async streams in C# 8.0?

Async streams let you return data one item at a time asynchronously using IAsyncEnumerable<T>. It’s like combining foreach with await.

Example:

public async IAsyncEnumerable<int> GetNumbersAsync()
{
    for (int i = 1; i <= 5; i++)
    {
        await Task.Delay(500);
        yield return i;
    }
}
await foreach (var num in GetNumbersAsync())
{
    Console.WriteLine(num);
}

Use Cases:

  • Reading data from a file or API
  • Streaming large datasets
  • Avoiding blocking UI or threads

In Short:

Async streams = streaming data + async. You get items as they become available without blocking your app.


51.What is the purpose of the sealed keyword in C#?

The sealed keyword in C# is used to prevent a class from being inherited or to prevent a method from being overridden in a derived class.

When Used with Classes:

sealed class FinalClass
{
    // This class cannot be inherited
}

Any attempt to inherit from FinalClass will cause a compile-time error.

When Used with Methods:

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

class Derived : Base
{
    public sealed override void Show() { }
}

Here, Show() can’t be overridden further by any subclass of Derived.

Use Cases:

  • Lock down a class for security or design reasons
  • Prevent further modification of critical logic
  • Improve performance in some cases

In Simple Words:

sealed = “No further changes allowed”
You use it when you want to stop others from extending your class or method.

Let me know if you want a real-world example with sealed classes!


52.Explain the difference between shallow copy and deep copy in C#.

The main difference lies in how the object’s references are copied:

Shallow Copy:

  • Copies the top-level object.
  • References inside the object still point to the same memory.
var copy = original; // or MemberwiseClone()

Changes to nested objects in the copy will affect the original.

Deep Copy:

  • Copies the object and all nested objects.
  • Entire structure is duplicated, not just referenced.

You usually implement it manually or use serialization.

In Simple Words:

  • Shallow Copy = “Duplicate the box, share what’s inside.”
  • Deep Copy = “Duplicate the box and everything inside it.”

Example:

class Person
{
    public string Name;
    public Address Address;
}

class Address
{
    public string City;
}

If you shallow copy a Person, both copies share the same Address.


53.What is the purpose of is and as operators in C#?

Both is and as are used for type checking and casting in C#:

is Operator:

Checks if an object is of a specific type.

if (obj is string)
{
    Console.WriteLine("It's a string!");
}

Returns true if the object is of that type.

as Operator:

Tries to cast an object to a type. If it fails, it returns null instead of throwing an exception.

string str = obj as string;

if (str != null)
{
    Console.WriteLine("Cast successful");
}

Safe casting without exceptions.

In Simple Words:

  • is = “Are you this type?”
  • as = “Try to become this type — if not, give me null”

54.What is the difference between throw and throw ex in exception handling?

Both are used to re-throw exceptions, but there’s an important difference in how they preserve the original stack trace.

throw (Recommended)

catch (Exception ex)
{
    throw; // Re-throws the original exception
}

Keeps the original stack trace, which helps in debugging the actual source of the error.

throw ex

catch (Exception ex)
{
    throw ex; // Re-throws the caught exception
}

Resets the stack trace, making it look like the exception started in the catch block — which can hide the real cause.

In Simple Words:

  • throw = “Continue throwing it exactly as it happened.”
  • throw ex = “Throw it again, but overwrite its history.”

Best Practice:

Always use just throw when you want to re-throw an exception without losing debugging info.

Let me know if you’d like an example to show the difference in stack traces!


55.How do you implement an interface explicitly in C#?

Explicit interface implementation means implementing interface members so they can only be accessed through the interface, not directly through the class.

Example:

interface IPrinter
{
    void Print();
}

class Document : IPrinter
{
    void IPrinter.Print()
    {
        Console.WriteLine("Printing document...");
    }
}

Usage:

Document doc = new Document();
doc.Print();         //  Error: Print is not accessible

IPrinter printer = doc;
printer.Print();     //  Works

When to Use:

  • When two interfaces have methods with the same name
  • To hide interface methods from the public class API
  • To give more control over how interface members are exposed

In Simple Words:

Explicit implementation means:
“You can use this method, but only if you treat me as the interface.”


56.What is the difference between abstract and virtual methods?

57.What are indexers in C#, and how are they different from properties?

58.What is the use of the params keyword in C#?

59.What is the difference between compile-time and run-time polymorphism?

60.How can you implement immutability in a C# class, and why is it important in multi-threaded applications?

61.How does the .NET garbage collector work under the hood, and how can you tune its behavior for large-scale applications?

62.What’s the difference between yield return and returning a list or array? When would you prefer each?

63.What is the difference between IEnumerable, ICollection, and IList, and when would you use each?

This article is a complete guide to the most commonly asked C# Interview Questions in 2025. Whether you’re a fresher brushing up on the basics or an experienced developer preparing for a technical round, this collection covers core C# topics like OOP, exception handling, collections, LINQ, async programming, and more. Each question is answered in a clear, human-friendly way to help you understand and explain concepts confidently.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *