C# Interview Questions (for Seniors)
Can you describe just-in-time compilation (JIT) in the context of C#?
Just-In-Time (JIT) Compilation is a technique used by the .NET runtime to provide significant performance improvements by compiling the Intermediate Language (IL) bytecode to native machine code at runtime.
Instead of pre-compiling the entire code to native code before executing, the JIT compiler optimizes and compiles only the required methods at runtime, greatly reducing load times and memory usage.
The main benefits of JIT compilation in the context of C# are:
- Faster application startup: Because only the necessary parts of the code are compiled, the application starts up more quickly.
- Better memory usage: Unused IL bytecode is never converted to native code, leading to lower memory usage.
- Platform-specific optimization: Native code is generated specifically for the runtime platform, allowing better optimization and performance.
The process followed by the JIT compiler in the context of C# consists of three stages:
- Loading the IL bytecodes: The CLR loads the required IL bytecode of the method to be executed.
- Compiling IL bytecodes to native code: The JIT compiler compiles the IL bytecodes to native machine code.
- Executing the native code: The generated native code is executed.
What is the difference between a normal class property and a computed class property in C#?
A normal class property is a simple property that holds a value and includes a getter and/or a setter method. These properties can be used to store and retrieve data for an object. The setter is responsible for setting the property value, and the getter is responsible for returning the property value.
A computed class property, also known as a calculated property, is a property that does not store any data but rather computes its value based on other property values within the class. Computed properties only have a getter method, which returns the calculated result, and do not have a setter method.
How can you implement a custom awaitable type in C#?
To implement a custom awaitable type in C#, you need to follow these steps:
- Create a class that represents the awaitable type.
- Implement the
INotifyCompletion
interface in the class for notifying when the operation is complete. - Add a method named
GetAwaiter
that returns an instance of the class itself. - Implement the
IsCompleted
property in the class as part of the awaitable pattern. - Add a method named
OnCompleted(Action continuation)
to the class which takes an action that will be executed when the operation is complete. - Implement the
GetResult
method, which will return the result of the asynchronous operation.
Why would you use the System.Reflection namespace, and how does it relate to C#?
System.Reflection is a namespace in .NET that provides functionality to obtain type information (metadata) about classes, objects, and assemblies at runtime. It allows developers to inspect and interact with the code in a dynamic manner, providing the ability to:
- Examine type information such as properties, fields, events, and methods.
- Create and manipulate instances of objects.
- Invoke methods and access fields/properties on instances.
- Discover and examine attributes applied to types and members.
- Load and interact with assemblies.
What are expression trees in C# and how can they be used?
Expression trees in C# are a data structure that represents code — specifically, Expressions — in a tree-like format, where each node is an object that represents a part of the expression. Expression trees enable developers to inspect, manipulate, or interpret code in a structured way at runtime. They allow operations such as modification, compilation, and execution of the expressed code.
Expression trees are mainly used in scenarios such as:
- Building dynamic LINQ queries for data manipulation.
- Dynamic code generation for performance-critical code paths.
- Serialization and deserialization of expression trees.
- Analyzing lambda expressions for parallelism or converting them to another form.
What is the real-world use case for the ‘yield’ keyword in C#?
The yield
keyword in C# is used in iterator methods to create a stateful iterator and return a sequence of values on-the-fly, without storing the entire sequence in memory.
It generates a custom implementation of the IEnumerator<T>
interface based on the code in the iterator method and remembers the current execution state between MoveNext()
calls.
This lazy evaluation of the iterator improves memory usage and performance, especially for large or infinite sequences.
Real-world use cases for the yield
keyword include:
- Implementing custom collections or sequences that need to support
foreach
iteration. - Generating an infinite sequence of values or a sequence that should not be stored in memory.
- Processing large data sets or streaming data incrementally without consuming a lot of memory.
Explain the role of the ‘volatile’ keyword in C#.
The volatile
keyword in C# is applied to fields to indicate that they can be accessed by multiple threads and that the field’s value may change unexpectedly, due to optimizations performed by the .NET runtime or underlying hardware.
When a field is marked as volatile
, the compiler and the runtime will not reorder or cache its read and write operations, ensuring that the most recent value is always read and that writes are immediately visible to other threads.
This provides a memory barrier, forcing atomic read and write operations and preventing unexpected side effects due to optimizations.
The volatile
keyword should be used in scenarios where multiple threads must access a shared field, and proper synchronization is required to maintain the consistency of data.
What are weak references, and when would you use them in C#?
In C#, weak references are references to objects that aren’t strong enough to prevent these objects from being garbage collected.
They allow you to maintain a reference to an object as long as the object is alive in memory, but without preventing garbage collection (GC) from reclaiming the object when memory pressure increases.
With weak references, you can access the object as long as it’s still in memory, but it will not prevent the GC from collecting the object if required.
Weak references are useful in scenarios where you want to hold a reference to a large object for caching purposes but do not want to prevent the object from being garbage collected if the memory pressure increases.
To use a weak reference in C#, you create an instance of the WeakReference
or WeakReference<T>
class.
Describe how to implement a custom attribute in C#.
To implement a custom attribute in C#, you follow these steps:
- Create a class that derives from the
System.Attribute
class. - Add properties, fields, or methods to the class as required to store metadata.
- Apply the attribute to elements in your code (such as classes, methods, or properties) by using the attribute syntax.
Explain the concept of memory-efficient array handling in C# using ArraySegment.
The ArraySegment<T>
structure in C# provides a memory-efficient way of handling arrays by allowing you to work with a segment of an existing array. This is useful in scenarios where you need to process a portion of a large array and want to avoid memory overhead caused by creating new subarrays.
The ArraySegment<T>
structure represents a contiguous range of elements within an array and provides properties, such as Array
, Offset
, and Count
, to access the underlying array and the segment boundaries.
What is the Roslyn Compiler Platform, and how does it relate to C#?
The Roslyn Compiler Platform is a set of open-source compilers, code analysis APIs, and code refactoring tools developed by Microsoft for C# and Visual Basic .NET (VB.NET) languages.
Roslyn exposes a powerful code analysis and transformation API, allowing developers to create more advanced static code analyzers, code fixers, and refactoring tools.
Roslyn’s relation to C#:
- It provides the default C# compiler which transforms C# code into Microsoft Intermediate Language (MSIL) code.
- It offers a modern C# language service implementation for Visual Studio.
- It lets developers take advantage of the code analysis and manipulation APIs for deeper code insights and generation.
- It supports advanced features of modern C# versions like pattern matching, expression-bodied members, and async-await constructs.
Explain the concept of duck typing and how it can be achieved in C#.
Duck typing refers to a programming concept in which the type of an object is determined by its behavior (methods and properties) rather than its explicit class or inheritance hierarchy.
In other words, duck typing is based on the principle: “If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.”
C# is a statically typed language, which means duck typing is not directly supported by the language. However, you can achieve a form of duck typing in C# using dynamic
keyword or reflection.
But it’s important to note that using dynamic
may bring performance overhead, and you lose some compile-time safety. Errors will occur at runtime if the method or property doesn’t exist on the object.
What is the difference between GetHashCode and Equals methods in C#?
GetHashCode
and Equals
methods are members of the System.Object
class, the base class for all objects in C#. They are used to compare objects for equality and serve different purposes:
GetHashCode
: This method returns an integer (hash code) representation of the object. It is primarily used by hashing-based collections likeDictionary
, HashSet, etc., to optimize the object’s lookup and storage. When implementing this method, you should make sure objects considered equal have the same hash code value.Equals
: This method checks whether two objects are equal in their content. It uses their data members to determine equality. By default, theEquals
method will compare object references, but it can be overridden to provide custom comparison logic based on the actual object content (like comparing properties).
When you override the Equals
method, you should also override the GetHashCode
method to maintain consistency between the two methods, ensuring objects that are considered equal have the same hash code.
Describe parallel programming support in C# and its benefits.
Parallel programming support in C# is provided by the System.Threading
, System.Threading.Tasks
, and System.Collections.Concurrent
namespaces. These parallel execution features allow developers to write applications that leverage modern multi-core and multi-processor hardware for better performance and responsiveness.
Key parallel programming features in C#:
- Task Parallel Library (TPL): Provides a high-level abstraction for executing tasks concurrently, simplifying the parallelism work like managing threads, synchronization, and exception handling.
- Parallel LINQ (PLINQ): A parallel execution version of standard LINQ, enabling developers to easily parallelize data-intensive query operations efficiently.
- Concurrent Collections: Collections like
ConcurrentDictionary
,ConcurrentQueue
,ConcurrentBag
, andConcurrentStack
provide thread-safe data structures to help manage shared state in parallel applications.
Benefits of parallel programming in C#:
- Improved performance by taking advantage of multi-core and multi-processor systems.
- Increased responsiveness by executing time-consuming tasks concurrently without blocking the UI thread.
- Simplified parallel programming through high-level abstractions provided by TPL and PLINQ.
- Better utilization of system resources leading to more efficient, scalable applications.
How do you perform compile-time code generation in C#, and what are the benefits?
Compile-time code generation in C# can be achieved using Source Generators, which are introduced in C# 9.0 and .NET 5. Source Generators are components that run during compilation and can inspect, analyze, and generate additional C# source code to be compiled alongside the original code.
A Source Generator is a separate assembly containing one or more classes implementing the ISourceGenerator
interface. Visual Studio and the .NET build system will discover Source Generators with the appropriate project references and will run them during the compilation process.
Benefits of compile-time code generation:
- Improved performance: Code generation at compile-time reduces runtime overhead.
- Code reduction: Automatically generating repetitive or boilerplate code reduces the code a developer needs to write and maintain.
- Security: Generated code is verified and secured before runtime, preventing security vulnerabilities arising from runtime code generation.
- Extensibility: Source Generators enable the creation of advanced libraries and frameworks, which can further enhance and optimize code generation and manipulation.
What is stackalloc in C#, and when should it be used?
In C#, stackalloc
is a keyword that allows you to allocate a block of memory on the stack.
int* block = stackalloc int[100];
Normally, when we work with arrays in C#, they are created on the heap. This introduces a level of overhead, as memory must be allocated and then garbage collected when the object is no longer in use. When dealing with large arrays or high performance code, the impact of this can sometimes be significant.
The stackalloc
keyword bypasses this by creating the array directly on the stack. This has 3 major implications:
- Performance: Generally, allocating memory on the stack is faster than allocating it on the heap. There’s no need to worry about garbage collection, as the memory will be automatically reclaimed when the method exits. This makes it highly efficient for small arrays.
- Memory Limit: The stack is a far more limited resource compared to the heap. The exact size depends on settings and other factors, but it’s typically in the region of 1MB. This makes
stackalloc
unsuitable for larger arrays. - Lifespan: Memory allocated on the heap can exist as long as your application does, whereas memory allocated on the stack only exists until the end of the current method. Any attempt to work with stack-allocated memory outside of this will lead to issues.
The typical use cases for stackalloc
are high performance scenarios that involve relatively small arrays, such as graphical or mathematical operations. Be aware that improper use can easily lead to stack overflows, causing your application to crash.
Consider using stackalloc
if the following cases are true:
- You have a small array (typically few hundred elements max).
- The array is local to your method and doesn’t need to be returned or passed elsewhere.
- The overhead of garbage collection has a significant impact on performance in your use case.
Explain the Tuple class and its use case in C#.
The Tuple class in C# represents a fixed-size, immutable collection of heterogeneously-typed elements. It’s part of the System
namespace and provides a simple way to group objects without defining custom domain-specific classes.
Tuples are useful in scenarios where you need to return multiple values from a method or store or pass around data without creating a specific data structure.
What are local functions in C# 7.0, and how can they be used?
Local functions, introduced in C# 7.0, are functions defined within the scope of another method.
They enable you to declare a helper function inside the method that needs it, keeping the helper function private and avoiding clutter in the class level namespace.
Local functions offer advantages like:
- Better organization and encapsulation of functionality.
- Access to the containing method’s variables, allowing them to share state easily.
- Limited accessibility, as local functions are not visible outside their containing method.
Explain the concept of marshaling in .NET and its application in C#.
Marshaling is the process of converting the types, objects, and data structures of one runtime environment to another, especially in the context of communication between managed (.NET) code and unmanaged (native) code.
Marshaling is widely used in .NET when you want to interact with components developed in other programming languages, operating systems, or hardware and software platforms (e.g., COM, Win32 API, or other third-party libraries).
In C#, marshaling is facilitated by the System.Runtime.InteropServices
namespace, providing required attributes, methods, and structures to:
- Define the layout of structures and unions in memory.
- Map managed types to unmanaged types.
- Specify calling conventions for unmanaged functions.
- Allocate and deallocate unmanaged memory.
—
ntechdevelopers