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.
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 aspublic
, 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
Aprotected
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 baseObject
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 theoverride
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:
Feature | Abstract Class | Interface |
---|---|---|
Inheritance | Single inheritance only | Multiple interfaces can be implemented |
Method Implementation | Can have both abstract & normal methods | No implementation (except default methods in C# 8+) |
Constructors | Can have constructors | Cannot have constructors |
Fields/State | Can have fields and properties | Cannot have fields (only properties) |
Access Modifiers | Allowed | Not 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:
Feature | Extension Method |
---|---|
Must be in a | static class |
Declared as | static method |
First parameter | Uses this keyword |
Can extend | Classes, interfaces, even sealed types |
Commonly used in | LINQ, 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:
Feature | Delegate |
What it does | Refers to methods |
Supports parameters | Yes |
Return values | Yes |
Multicast? | Yes, you can chain multiple methods |
Bonus: Action & Func
C# also gives us built-in delegates:
Action<T>
→ for methods that return voidFunc<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
Feature | Delegate | Event |
---|---|---|
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) |
Purpose | General-purpose method reference | For 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:
Method | Description |
---|---|
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:
Feature | IEnumerable | IQueryable |
---|---|---|
Namespace | System.Collections.Generic | System.Linq |
Execution Location | In-memory (client-side) | Database or remote source (server-side) |
Use Case | Small/loaded collections | Large datasets via Entity Framework, LINQ to SQL |
Performance | Less efficient with large datasets | More efficient due to query translation |
Query Transformation | Not supported | Supports 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
Feature | Benefit |
---|---|
Type Safety | Catch errors at compile time |
Code Reusability | One method/class works for any data type |
Performance | Avoids unnecessary boxing/unboxing |
Cleaner Code | No 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:
Feature | Description |
---|---|
.HasValue | Returns true if it’s not null |
.Value | Gets the value (throws error if null) |
?? operator | Defines a default if value is null |
Nullable vs Non-Nullable
Type | Nullable Type |
---|---|
int | int? |
bool | bool? |
DateTime | DateTime? |
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
var
– Type 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.
object
– Base 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.
dynamic
– Resolved 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
Feature | var | object | dynamic |
---|---|---|---|
Type known at | Compile time | Compile time | Runtime |
Flexibility | Medium | High | Very High |
Type Safety | Yes | Limited | No |
Need casting? | No | Yes (for value access) | ❌ No |
Use Case | Cleaner code | General-purpose storage | Interop, 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:
Term | Meaning |
---|---|
async | Marks a method that contains await and runs asynchronously |
await | Pauses the method until the awaited task is done |
Task / Task<T> | Represents an ongoing operation that may return a result |
Non-blocking | Your 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:
Feature | Thread | Task |
---|---|---|
Level | Low-level (direct thread) | High-level (abstraction over threads) |
Control | Manual (start/abort/suspend) | Managed by .NET runtime |
Performance | Heavier, more overhead | Lightweight, uses thread pool |
Exception Handling | Manual | Easier with built-in support |
Suitable For | Long-running, dedicated work | Short background work, async code |
Supports await ? | No | Yes |
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 modernasync
/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:
Keyword | Purpose |
---|---|
try | Wraps code that might cause an exception |
catch | Catches and handles specific or general exceptions |
finally | Optional block that runs always (cleanup, closing files etc.) |
throw | Manually 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:
Feature | Description |
---|---|
Cannot be instantiated | You can’t do new MathHelper() |
All members must be static | No instance members allowed |
Sealed automatically | Can’t be inherited |
Memory-efficient | No 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:
Rule | Description |
---|---|
Must use partial keyword | Every part must include partial |
Must be in the same namespace | And the same assembly |
Can span multiple files | All 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 Type | Description |
---|---|
Type Pattern | Checks and casts in one go (is string msg ) |
Constant Pattern | Compares with a constant value (case 0: ) |
Relational Pattern | Compares using < , > , <= , etc. |
Logical Pattern | Combines patterns with and , or , not |
Property Pattern | Checks object properties ({ Name: "John" } ) |
Positional Pattern | Deconstructs 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) forSystem.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:
- You create objects using
new
. - As long as you’re using the object, it stays in memory.
- When no part of your code references that object anymore, it becomes eligible for garbage collection.
- 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 theusing
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#:
Attribute | Purpose |
---|---|
[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 privatereadonly 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:
Feature | Task.WhenAll | Task.WhenAny |
---|---|---|
Completion | When all tasks complete ✅ | When any one task completes ✅ |
Return value | Task that completes with all results | Task<Task> (the first to finish) |
Common use case | Parallel processing with full results | Time-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 anytimeinit
= 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 constructorpublic InvalidAgeException(string message)
– lets you pass a custom error messagepublic 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.