Hey, C# devs! Ready for a new challenge?
We’re tackling 20 intermediate-level C# interview questions to level up your skills and amp up your problem-solving game.
Think you can handle it?
Let’s jump in!
How do you create a property in C#?
Answer
In C#, properties are members of a class that provide a flexible mechanism to read, write, or compute the value of private fields. Properties can be created using get
and set
accessors in the property definition.
A property can have a get
accessor, a set
accessor, or both, depending on whether you want the property to be read-only, write-only, or read-write.
Here’s an example of a property with both get
and set
accessors in C#:
public class Person
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
In C# 3.0 and later, you can use auto-implemented properties, which enables you to create properties without having to define the backing field explicitly:
public class Person
{
public string Name { get; set; }
}
What is the purpose of the ‘params’ keyword in C#?
Answer
The params
keyword in C# allows a method to accept a variable number of arguments of a specified type. Using the params
keyword, you can pass an array of values or separate comma-separated values to the method. Inside the method, the params
parameter is treated like an array.
Here’s an example of using the params
keyword:
public static class Utility
{
public static int Sum(params int[] values)
{
int sum = 0;
foreach (int value in values)
{
sum += value;
}
return sum;
}
}
You can call the Sum
method with any number of arguments, like this:
int result1 = Utility.Sum(1, 2, 3);
int result2 = Utility.Sum(1, 2, 3, 4, 5);
What are the characteristics of a static class in C#?
Answer
In C#, a static class is a class that cannot be instantiated or inherited. It can only have static members, and it cannot have instance members (like instance properties, methods, or fields). The main characteristics of a static class are:
- It is declared with the
static
keyword. - It cannot have a constructor (except the static constructor).
- It cannot be instantiated or inherited.
- All members of a static class must also be static.
- It can be accessed directly using the class name, without creating an instance.
A common use of static classes is for utility functions and extension methods. Here’s an example of a static class in C#:
public static class MathUtility
{
public static int Add(int x, int y)
{
return x + y;
}
public static int Subtract(int x, int y)
{
return x - y;
}
}
To use this static class, you simply call its methods like this:
int sum = MathUtility.Add(1, 2);
int difference = MathUtility.Subtract(3, 1);
How do you pass data between two forms in C#?
Answer
In C#, there are several ways to pass data between two forms in a Windows Forms application. One common approach is to use public properties or methods to expose the data that needs to be passed. Here’s an example:
- Create a second form with a public property that will hold the data to be passed.
public partial class SecondForm : Form
{
public string Data { get; set; }
public SecondForm()
{
InitializeComponent();
}
}
- In the first form, instantiate the second form and set the
Data
property with the data you want to pass.
public partial class FirstForm : Form
{
public FirstForm()
{
InitializeComponent();
}
private void OpenSecondFormButton_Click(object sender, EventArgs e)
{
SecondForm secondForm = new SecondForm();
secondForm.Data = "Data to be passed";
secondForm.Show();
}
}
In this example, when the user clicks the OpenSecondFormButton
, the second form opens, and the Data
property of the second form contains the data passed from the first form.
What is the difference between a class and an object in C#?
Answer
In C#, a class is a blueprint for creating objects. It defines the structure, properties, and methods that objects of that class will have. Classes are reference types, and they can inherit from other classes to extend or override functionality.
An object, on the other hand, is an instance of a class. It is created from the class definition and occupies memory when instantiated. Objects are instances of classes, and they contain the data (fields) and behaviors (methods) that are defined in the class.
For example, consider a Car
class:
public class Car
{
public string Model { get; set; }
public int Year { get; set; }
public void Drive()
{
Console.WriteLine("The car is driving.");
}
}
In this example, Car
is the class, and it defines the structure and behavior of car objects. To create objects (instances) of the class, you use the new
keyword, like this:
Car car1 = new Car();
car1.Model = "Toyota";
car1.Year = 2020;
Car car2 = new Car();
car2.Model = "Honda";
car2.Year = 2021;
In this example, car1
and car2
are objects (instances) of the Car
class, and they have their own individual sets of properties and methods.
What is a delegate in C#?
Answer
A delegate in C# is a type that defines a method signature and can hold a reference to a method with a matching signature. Delegates are similar to function pointers in C++, but they are type-safe and secure. They are commonly used for implementing events and callbacks.
Delegates can be used to pass methods as arguments, assign them to class properties, or store them in collections. Once a delegate is defined, it can be used to create delegate instances that point to methods with the same signature.
Here’s an example of a delegate in C#:
public delegate void MyDelegate(string message);
public class MyClass
{
public static void DisplayMessage(string message)
{
Console.WriteLine("Message: " + message);
}
}
public class Program
{
public static void Main()
{
// Create a delegate instance
MyDelegate myDelegate = MyClass.DisplayMessage;
// Invoke the method through the delegate
myDelegate("Hello, World!");
}
}
In this example, MyDelegate
is a delegate that defines the method signature for methods that take a string
argument and return void
. The Main
method creates a delegate instance, assigns the MyClass.DisplayMessage
method to it, and invokes the method through the delegate.
How do you implement polymorphism in C#?
Answer
Polymorphism is one of the fundamental principles of object-oriented programming. It allows objects of different classes to be treated as objects of a common superclass. In C#, there are two types of polymorphism: compile-time (method overloading) and runtime (method overriding and interfaces).
Method overriding and interfaces are the common ways to implement runtime polymorphism. Here’s an example using method overriding:
// Base class
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("The animal speaks.");
}
}
// Derived class
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("The dog barks.");
}
}
public class Program
{
public static void Main()
{
Animal myAnimal = new Dog();
myAnimal.Speak(); // Output: The dog barks.
}
}
In this example, we define a base class Animal
and a derived class Dog
. The Speak
method in the Animal
class is marked as virtual
, allowing its behavior to be overridden by derived classes.
The Dog
class overrides the Speak
method with its own implementation. When we create a Dog
object and assign it to an Animal
variable, the overridden method in the Dog
class is called at runtime, exhibiting polymorphism.
To implement polymorphism via interfaces, you can define an interface and then have classes implement the interface methods. For example:
public interface ISpeak
{
void Speak();
}
public class Dog : ISpeak
{
public void Speak()
{
Console.WriteLine("The dog barks.");
}
}
public class Cat : ISpeak
{
public void Speak()
{
Console.WriteLine("The cat meows.");
}
}
public class Program
{
public static void Main()
{
ISpeak myAnimal = new Dog();
myAnimal.Speak(); // Output: The dog barks.
myAnimal = new Cat();
myAnimal.Speak(); // Output: The cat meows.
}
}
In this example, we define an ISpeak
interface with a Speak
method. The Dog
and Cat
classes implement this interface and provide their own implementation of the Speak
method. We can create and assign objects of these classes to an ISpeak
variable, and the appropriate implementation of the Speak
method is called at runtime, demonstrating polymorphism.
What is a struct in C#?
Answer
A struct (short for “structure”) in C# is a value type that can contain fields, properties, methods, and other members like a class.
However, unlike classes, which are reference types, structs are value types and are stored on the stack. Structs do not support inheritance (except for implicitly inheriting from System.ValueType), and they cannot be used as a base for other classes or structs.
Structs are useful for representing small, lightweight objects that have a small memory footprint and do not require garbage collection overhead. They are commonly used to represent simple data structures, such as points in a 2D space, dates, time intervals, and colors.
Here’s an example of a struct in C#:
public struct Point
{
public int X { get; set; }
public int Y { get; set; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public double DistanceToOrigin()
{
return Math.Sqrt(X * X + Y * Y);
}
}
public class Program
{
public static void Main()
{
Point p = new Point(3, 4);
Console.WriteLine("Distance to origin: " + p.DistanceToOrigin()); // Output: Distance to origin: 5
}
}
In this example, we define a Point
struct with properties X
and Y
, a constructor, and a method DistanceToOrigin
. We can create Point
instances and use their methods like we would with class instances.
What is the difference between ‘throw’ and ‘throw ex’ in exception handling?
Answer
In C#, when handling exceptions, it’s important to understand the difference between throw
and throw ex
:
-
throw
: When you catch an exception and want to rethrow the original exception, you use thethrow
statement without specifying any exception object. This preserves the original exception’s stack trace and allows the upstream exception handler to access the full stack trace. -
throw ex
: When you catch an exception and want to throw a new or modified exception object, you use thethrow ex
statement, whereex
is an instance of an exception. This replaces the original exception’s stack trace, and the upstream exception handler will only see the stack trace starting from the point wherethrow ex
was called.
Here’s an example:
public class Example
{
public void Method1()
{
try
{
// Code that might raise an exception
}
catch (Exception ex)
{
// Logging or other exception handling code
// Rethrow the original exception
throw;
}
}
public void Method2()
{
try
{
// Code that might raise an exception
}
catch (Exception ex)
{
// Logging or other exception handling code
// Rethrow a modified exception
throw new ApplicationException("An error occurred in Method2.", ex);
}
}
}
In Method1
, we catch an exception and rethrow it using throw
. The upstream exception handler can access the original stack trace of the exception. In Method2
, we catch an exception and throw a new ApplicationException
with the original exception as its inner exception using throw ex
. The upstream exception handler will see the stack trace starting from the point where throw ex
was called.
What is a nullable type and how is it used in C#?
Answer
Nullable types in C# are value types that can have a ‘null’ value. By default, value types cannot have a ‘null’ value, as they have a default value (like 0 for int, false for bool, etc.). To create a nullable type, you can use the built-in Nullable<T>
structure or the syntactic sugar ?
modifier.
Nullable types are useful when you need to indicate the absence of a value or when working with data sources (like databases) that might contain missing values.
Creating nullable types:
Nullable<int> nullableInt = null;
int? anotherNullableInt = null;
Nullable types can be checked for having a value using the HasValue
property and retrieved using the Value
property. Alternatively, you can use the GetValueOrDefault
method to get the value if it exists or a default value:
int? x = null;
if (x.HasValue)
{
Console.WriteLine("x has a value of: " + x.Value);
}
else
{
Console.WriteLine("x is null"); // This will be printed
}
int y = x.GetValueOrDefault(-1); // y = -1, because x is null
C# also supports nullable value type conversions and null coalescing using the ??
operator to provide a default value when a nullable type is ‘null’:
int? a = null;
int b = a ?? 10; // b = 10, because a is null
Describe the concept of LINQ in C#.
Answer
Language Integrated Query (LINQ) is a feature introduced in C# 3.0 (.NET Framework 3.5) that provides a set of query operators and a unified query syntax for querying, filtering, and transforming data from various types of data sources like collections, XML, databases, or even custom sources.
LINQ allows you to write type-safe, powerful, and expressive queries directly in C# with the benefits of full support from the compiler, static typing, and IntelliSense.
LINQ offers two types of syntax for writing queries:
- Query Syntax: This is a more SQL-like, declarative syntax that provides a high level of abstraction and readability. Query syntax requires the
from
keyword (retrieves the data),select
keyword (specifies the output), and optional keywords (such aswhere
,group
, ororderby
).
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
var evenNumbersQuery = from n in numbers
where n % 2 == 0
orderby n
select n;
foreach (var n in evenNumbersQuery)
{
Console.WriteLine(n); // 2, 4, 6
}
- Method Syntax: This is based on LINQ extension methods of the
System.Linq.Enumerable
class and uses lambda expressions to manipulate the data. Method syntax provides a more functional approach but may be less readable if the query is complex.
var evenNumbersMethod = numbers.Where(n => n % 2 == 0).OrderBy(n => n);
foreach (var n in evenNumbersMethod)
{
Console.WriteLine(n); // 2, 4, 6
}
LINQ supports a variety of providers like LINQ to Objects (for in-memory collections), LINQ to XML (for XML documents), and LINQ to Entities (for entity framework), enabling developers to work with different data sources using a similar query syntax.
What is a lambda expression in C#?
Answer
A lambda expression in C# is an anonymous function that is used to create inline delegates, expression trees, and expressions for LINQ queries or other functional programming constructs. Lambda expressions rely on the =>
operator (called the lambda operator) to separate the input parameters and the expression or statement block.
Lambda expressions can be used for both expression and statement lambdas:
- Expression Lambda: This consists of an input parameter list, followed by the lambda operator, and a single expression. An expression lambda automatically returns the result of the expression without the need for an explicit
return
statement.
Func<int, int, int> sum = (a, b) => a + b;
int result = sum(1, 2); // result = 3
- Statement Lambda: This is similar to an expression lambda but contains a statement block enclosed in braces
{}
instead of a single expression. A statement lambda allows for multiple statements and requires an explicitreturn
statement if a value must be returned.
Func<int, int, int> sum = (a, b) => {
int result = a + b;
return result;
};
int result = sum(1, 2); // result = 3
Lambda expressions are a concise and efficient way of representing delegates or expressions, and are commonly used with LINQ queries, event handlers, or higher-order functions that expect a function as an argument.
Describe the async and await keywords in C#. How can they be used together?
Answer
In C#, the async
and [await](https://www.bytehide.com/blog/await-csharp/ "Mastering Await in C#: From Zero to Hero")
keywords are used to write asynchronous code that can run concurrently without blocking the main execution thread. This allows for more efficient and responsive applications, particularly in cases of I/O-bound operations (e.g., calling web services, reading files, or working with databases) or CPU-bound operations that can be parallelized.
- async: The
async
keyword is added to a method to indicate that it’s an asynchronous method. An async method usually returns aTask
for void-returning methods orTask<TResult>
for methods that return a value of type TResult. An async method can contain one or moreawait
expressions.
public async Task<string> DownloadContentAsync(string url)
{
using (HttpClient client = new HttpClient())
{
var content = await client.GetStringAsync(url);
return content;
}
}
- await: The
await
keyword is used to pause the execution of an async method until an awaited task completes. It asynchronously waits for the task to complete and returns the result (if any) without blocking the main execution thread. Theawait
keyword can only be used inside an async method.
public async Task ProcessDataAsync()
{
var content = await DownloadContentAsync("https://example.com");
Console.WriteLine(content);
}
When using async
and await
together, ensure that you propagate the asynchronous behavior up the call stack by marking every calling method with the async
keyword and awaiting async methods in the method body. This “async all the way” approach ensures that the benefits of asynchronous programming are fully utilized throughout the application.
What is the Task Parallel Library (TPL) and how does it relate to C#?
Answer
The Task Parallel Library (TPL) is a set of APIs introduced in the .NET Framework 4.0 to simplify parallelism, concurrency, and asynchronous programming. It is part of the System.Threading.Tasks
namespace and provides a higher-level, more abstract model for working with multithreading, asynchrony, and parallelism than the traditional lower-level threading constructs (e.g., ThreadPool or Thread class).
TPL’s main components include Task
, Task<TResult>
, and Parallel
classes, which serve the following purposes:
- Task: Represents an asynchronous operation that doesn’t return a value. Tasks can be awaited using the
await
keyword, or you can attach continuations to them using theContinueWith
method.
Task.Run(() => {
Console.WriteLine("Task-based operation running on a different thread");
});
- Task: Represents an asynchronous operation that returns a value of type TResult. It derives from the
Task
class and has aResult
property that holds the result once the task is complete.
Task<int> task = Task.Run<int>(() => {
return 42;
});
int result = task.Result; // waits for the task to complete and retrieves the result
- Parallel: Provides parallel versions of
for
andforeach
loops, as well as anInvoke
method to execute multiple actions concurrently. TheParallel
class can automatically utilize multiple CPU cores or threads, allowing you to parallelize CPU-bound operations easily.
Parallel.For(0, 10, i => {
Console.WriteLine($"Parallel loop iteration: {i}");
});
The TPL allows developers to write efficient and scalable parallel, concurrent, and asynchronous code in C# without having to deal directly with low-level threading and synchronization primitives.
Can you explain covariance and contravariance in C#?
Answer
Covariance and contravariance are terms related to the type relationships in C# when dealing with generics, delegates, or arrays. They describe how a derived type can be used in place of a base type (or vice versa) when assigning variables, parameters, or return values.
- Covariance (from less derived type to more derived type): Covariance allows you to use a more derived type instead of the original generic type parameter. In C#, covariance is supported for arrays, interfaces with generic parameters, and delegates with matching return types.
IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings; // Covariant assignment
- Contravariance (from more derived type to less derived type): Contravariance allows you to use a less derived type instead of the original generic type parameter. In C#, contravariance is supported for interfaces with generic parameters marked with the
in
keyword and delegates with matching input parameters.
IComparer<object> objectComparer = Comparer<object>.Default;
IComparer<string> stringComparer = objectComparer; // Contravariant assignment
// Using contravariant delegate
Action<object> setObject = obj => Console.WriteLine(obj);
Action<string> setString = setObject;
Covariance and contravariance can help increase the flexibility and reusability of your code by allowing assignments and conversions between different type hierarchies without explicit type casting.
Describe the differences between early binding and late binding in C#.
Answer
Early binding and late binding are two mechanisms in C# related to how the compiler and runtime resolve types, methods, or properties during program execution.
- Early Binding: Also known as static or compile-time binding, early binding refers to the process of resolving types, methods, or properties at compile time. With early binding, the compiler validates that the members being called exist and that they’re being called correctly regarding their accessibility, parameters, and return values. Early binding provides benefits like better performance (due to reduced runtime overhead) and type safety, as the compiler can catch and report errors during compilation. Example:
var myString = "Hello, World!";
int stringLength = myString.Length; // Early binding
- Late Binding: Also known as dynamic or runtime binding, late binding refers to the process of resolving types, methods, or properties at runtime. With late binding, the actual binding of members occurs only when the code is executed, which can lead to runtime errors if the members don’t exist or are not accessible.
Example:
dynamic someObject = GetSomeObject();
someObject.DoSomething(); // Late binding
Late binding is often used when the exact types or members aren’t known at compile time or when working with COM objects, reflection, or dynamic types. Late binding has certain advantages, such as allowing for more flexibility, extensibility, and the ability to work with types that aren’t known at compile time.
However, it also comes with disadvantages such as increased runtime overhead (due to additional type checks, method lookups, or dynamic dispatch) and a loss of type safety (due to the possibility of runtime errors).
What is the Global Assembly Cache (GAC) in C#?
Answer
The Global Assembly Cache (GAC) is a centralized repository or cache for storing and sharing .NET assemblies (DLLs) on a machine. The GAC is part of the .NET Framework and is used to avoid DLL conflicts and enable side-by-side execution of different versions of the same assembly.
Assemblies stored in the GAC must have a strong name, which consists of the assembly name, version number, culture information (if applicable), and a public key token (generated from the developer’s private key). This strong name allows the GAC to uniquely identify and manage each assembly and its versions.
To install an assembly in the GAC, you can use the gacutil
utility, or you can drag and drop the assembly into the GAC folder using Windows Explorer.
gacutil -i MyAssembly.dll
As a developer, you typically don’t need to explicitly reference assemblies from the GAC, as the .NET runtime automatically looks for them in the GAC before it searches other locations. However, it’s essential to be aware of the GAC and understand how it impacts assembly sharing, versioning, and deployment scenarios.
Explain how the ‘var’ keyword works in C#, and give a practical example of its use.
Answer
In C#, the var
keyword is used to implicitly type a local variable when you don’t need or want to explicitly specify the type. When you use the var
keyword, the C# compiler infers the type of the variable based on the value assigned to it during initialization. The variable is still strongly-typed at compile-time, just like any other typed variable.
It’s important to note that the var
keyword can only be used with local variables, and the variable must be initialized with a value during its declaration (otherwise, the compiler cannot infer the type).
Using the var
keyword can provide benefits like increased readability and ease of refactoring, particularly when dealing with complex or verbose types (e.g., generics or anonymous types).
Example:
var number = 42; // int
var message = "Hello, World!"; // string
var collection = new List<string>(); // List<string>
var anonymousType = new { Name = "John", Age = 30 }; // Anonymous type
In the example above, the compiler infers the types of the variables based on their assigned values. Using var
here can make the code more concise and readable.
However, it’s important to strike the right balance between readability and maintainability. In cases where using var
might decrease the readability or understandability of the code, it’s better to use an explicit type.
What is a thread-safe collection, and can you give an example of one in C#?
Answer
A thread-safe collection is a data structure designed to be safely and correctly accessed or modified by multiple threads concurrently. In general, most built-in .NET collection classes (e.g., List<T>
, Dictionary<TKey, TValue>
, etc.) are not thread-safe, which means that concurrent access or modifications can lead to unexpected results or data corruption.
To provide thread-safe collections, the .NET Framework offers the System.Collections.Concurrent
namespace, which includes several thread-safe collection classes, such as:
-
ConcurrentBag<T>
: An unordered collection of items that allows duplicates and has fastAdd
andTake
operations. -
ConcurrentQueue<T>
: A first-in, first-out (FIFO) queue that supports concurrentEnqueue
andTryDequeue
operations. -
ConcurrentStack<T>
: A last-in, first-out (LIFO) stack that supports concurrentPush
andTryPop
operations. -
ConcurrentDictionary<TKey, TValue>
: A dictionary that supports thread-safeAdd
,Remove
, and other dictionary operations.
Example:
ConcurrentQueue<int> concurrentQueue = new ConcurrentQueue<int>();
Parallel.For(0, 1000, i => {
concurrentQueue.Enqueue(i);
});
int itemCount = concurrentQueue.Count; // itemCount = 1000
In the example above, the ConcurrentQueue<int>
instance safely handles concurrent Enqueue
operations without the need for manual locking or synchronization.
Keep in mind that using thread-safe collections can come with some performance overhead, so you should carefully evaluate the trade-off between thread-safety and performance in your specific use case.
Explain how exception handling works in C# using try-catch-finally blocks.
Answer
Exception handling in C# is a mechanism for dealing with unexpected or exceptional conditions that may occur during program execution. It helps to maintain the normal flow of the program and ensure that resources are properly released even when an exception is encountered.
In C#, exception handling is primarily done using the try
, catch
, and finally
blocks:
- try: The
try
block contains the code that might throw an exception. You enclose the code that may raise exceptions within this block. - catch: The
catch
block is used to handle the specific exception that is thrown within thetry
block. You can have multiplecatch
blocks for different exception types. The first matchingcatch
block that can handle the exception type will be executed. - finally: The
finally
block is used to execute code regardless of whether an exception was thrown or not. This block is optional, and it’s typically used for resource cleanup (e.g., closing files or database connections).
Example:
try
{
int result = divide(10, 0);
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"Caught DivideByZeroException: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Caught a generic exception: {ex.Message}");
}
finally
{
Console.WriteLine("This will execute, no matter what.");
}
In the example above, the try
block attempts to execute a division operation that throws a DivideByZeroException
. The catch
block handling DivideByZeroException
is executed, and the exception message is written to the console. Regardless of the exception, the finally
block will always execute, ensuring that any necessary cleanup is performed.
There you go — 20 intermediate-level C# questions! Don’t forget, our article series covers C# Interview Questions and Answers for all skill levels.
Just follow our content to stay in the know and tackle more C# challenges. Keep on coding, everyone!
Top comments (2)
Not only for interviews, but one could use this content for self-study as well. Perfect explanations for some of the essential concepts in C#.
This is a incredible article that helps one to be prepared for a job interview as well as using It just for studying. Thanks for sharing