C# / .NET Interview Questions and Answers: Part 2 – Types and Type Features

This Part focuses on foundational and modern types in C#, emphasizing their role in collections, performance, and design.
Read Other Parts:
What is the purpose of the dynamic
keyword in C#, and how does it differ from var
?
In C#, both var
and dynamic
are used for variable declaration without explicitly specifying the type, but they serve different purposes
var
: Introduced in C# 3.0, var
allows the compiler to infer the variable type at compile time based on the assigned value. Once inferred, the type is fixed and cannot change. All type checking is done at compile time, and IntelliSense support is available.
var number = 10; // 'number' is inferred as int
var text = "Hello"; // 'text' is inferred as string
dynamic
: Introduced in C# 4.0, dynamic
defers type resolution to runtime. This means the compiler does not know the type of the variable, and any operations on it are resolved at runtime. Consequently, IntelliSense support is limited, and type-related errors will only surface during execution.
dynamic value = 10;
value = "Hello"; // No compile-time error, type resolved at runtime
Console.WriteLine(value.Length); // Works if value is string, else runtime error
Key Differences:
Type Checking:
var
: Performed at compile time.dynamic
: Performed at runtime.
IntelliSense Support:
var
: Full support, as the type is known at compile time.dynamic
: Limited support, since the type is unknown until runtime.
Use Cases:
var
: Preferred when the type is evident from the assignment and does not change.dynamic
: Useful when interacting with COM objects, dynamic languages, or scenarios requiring runtime type determination.
What .NET engineers should know:
- 👼 Junior: Understand the basic use cases of
var
anddynamic
, and recognize thatvar
is type-safe at compile time, whereasdynamic
is resolved at runtime. - 🎓 Middle: Comprehend the implications of using
dynamic
, including performance considerations and potential runtime errors, and applydynamic
appropriately in scenarios like reflection or interoperability. - 👑 Senior: Evaluate and decide when to use
dynamic
to balance flexibility and type safety, and understand the internal workings of the Dynamic Language Runtime (DLR) that supportsdynamic
in C#.
📚 Resources:
What's the difference between dynamic and var? - Stack Overflow
What are Tuples
in C#, and what is their use case?
In C#, a tuple is a data structure that groups multiple elements, potentially of different types, into a single object. This allows for the convenient handling of related data without defining a separate class or structure.
Tuple example:
(double, int) t1 = (4.5, 3);
Console.WriteLine($"Tuple with elements {t1.Item1} and {t1.Item2}.");
// Output:
// Tuple with elements 4.5 and 3.
(double Sum, int Count) t2 = (4.5, 3);
Console.WriteLine($"Sum of {t2.Count} elements is {t2.Sum}.");
// Output:
// Sum of 3 elements is 4.5.
What .NET engineers should know about Tuples:
- 👼 Junior: Recognize that tuples allow for grouping multiple values into a single entity, facilitating the return or passing of various values from or to methods without creating custom types.
- 🎓 Middle / 👑 Senior: Be aware of the differences between reference tuples (
System.Tuple
) and value tuples (System.ValueTuple
), and their implications on performance and memory allocation.
📚Resources:
How does the Span<T> type improve performance in C#?
Span<T>
provides a safe, efficient way to work with slices of data without copying them.
It represents a contiguous region of memory (stack, heap, or unmanaged) and enables high-performance operations, such as parsing, slicing, and processing, without requiring allocations.
Span<T>
is stack-only and non-heap-allocating, making it ideal for low-latency, high-throughput scenarios.
int[] numbers = { 1, 2, 3, 4, 5 };
Span<int> slice = numbers.AsSpan(1, 3);
foreach (var num in slice)
{
Console.WriteLine(num); // Outputs: 2, 3, 4
}
No array copy — just a view into existing memory!
What .NET Engineers Should Know about Span<T>
:
- 👼 Junior: Understand that
Span<T>
provides a view into existing memory without requiring a copy. - 🎓 Middle: Use
Span<T>
for efficient string manipulation, array slicing, and buffer operations. - 👑 Senior: Design safe APIs that expose
Span<T>
, understand their lifetime rules, and avoid leakingSpan
outside stack-bound scopes.
📚 Resources:
Explain the role of the default literal in C# and its use cases.
The default literal (default
) was introduced in C# 7.1. Instead of writing default(T)
(verbose), you can write default
, and the compiler infers the type.
Useful for:
- Reducing boilerplate
- Improving code clarity
- Working with generics and nullable types
List<int> numbers = default; // Equivalent to List<int> numbers = default(List<int>);
T GetDefault<T>() => default;
What is an Expression Tree?

An expression tree in C# is a data structure that represents code in a tree-like format, where each node denotes an expression, such as a method call, binary operation, or constant value. Expression trees enable the dynamic creation, analysis, and execution of code at runtime, allowing developers to inspect and modify program behavior programmatically.
Code Example:
Here's how to create an expression tree that represents a simple addition operation (a + b
):
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
// Define the parameters for the expression: (a, b) => a + b
ParameterExpression paramA = Expression.Parameter(typeof(int), "a");
ParameterExpression paramB = Expression.Parameter(typeof(int), "b");
// Define the addition operation: a + b
BinaryExpression body = Expression.Add(paramA, paramB);
// Create the lambda expression: (a, b) => a + b
Expression<Func<int, int, int>> additionExpression = Expression.Lambda<Func<int, int, int>>(body, paramA, paramB);
// Compile the expression into executable code
Func<int, int, int> additionFunction = additionExpression.Compile();
// Execute the compiled expression
int result = additionFunction(5, 3);
Console.WriteLine($"5 + 3 = {result}");
}
}
In this example:
- We define two parameters,
a
andb
, usingExpression.Parameter
. - We create a binary expression that adds these two parameters using
Expression.Add
. - We then create a lambda expression representing the addition operation.
- Finally, we compile the expression into executable code and invoke it with sample values.
What .NET engineers should know:
👼 Junior:
- Should recognize that expression trees represent code in a hierarchical tree format, facilitating dynamic code manipulation.
- Be aware that expression trees are utilized in scenarios like LINQ to SQL, where queries written in C# are translated into SQL queries for database execution.
🎓 Middle:
- Expected to understand how to construct and traverse expression trees using the
System.Linq.Expressions
namespace, and how they enable the creation of dynamic queries and the compilation of code at runtime. - It should be capable of building custom expressions to create dynamic LINQ queries or develop dynamic code generation features within applications.
👑 Senior:
- The expertise to leverage expression trees for advanced scenarios, such as creating custom LINQ providers, developing dynamic rule engines, or implementing code analysis tools.
- Understand that constructing and compiling expression trees can introduce overhead compared to pre-compiled code. For example, dynamically creating and compiling an expression tree may be slower than hard-coded methods. However, the resulting delegates can execute with performance comparable to compiled code once compiled. Therefore, it's advisable to cache compiled delegates when the same expression tree is used multiple times to mitigate compilation overhead
📚 Resources:
What are records in C#, and how do they simplify immutable type design?
Records in C# provide a concise syntax for creating immutable reference types or value types with built-in support for value-based equality. They simplify the definition of data-centric classes by automatically implementing methods like equality checks (Equals
), hashing (GetHashCode
), and readable output (ToString
).
Records Example:
public record Person(string FirstName, string LastName);
public static class Program
{
public static void Main()
{
Person person = new("Nancy", "Davolio");
Console.WriteLine(person);
// output: Person { FirstName = Nancy, LastName = Davolio }
}
}
What .NET engineers should know about Record
:
- 👼 Junior: Records are a way to define immutable data-centric classes and compare them by value.
- 🎓 Middle: They reduce boilerplate by auto-generating methods like
Equals
,GetHashCode
, andToString
, and supportwith
expressions for non-destructive mutations. - 👑 Senior: Records support inheritance and pattern matching and can be used with positional parameters in derived record types. They are ideal for representing value objects in domain-driven design.
📚 Resources:
What are ref
locals and ref
returns in C#, and how do they differ from regular variables?
ref locals
and ref
returns allow for the return and storage of references to variables rather than their values. ref
locals and returns allow methods to return references to variables, enabling direct manipulation of the original data without copying.
Code example:
class Program
{
static ref int Find(int[] numbers, int target)
{
for (int i = 0; i < numbers.Length; i++)
if (numbers[i] == target)
return ref numbers[i];
throw new Exception("Not found");
}
static void Main()
{
int[] nums = { 1, 2, 3, 4 };
ref int numRef = ref Find(nums, 3);
numRef = 99; // directly modifies the array
Console.WriteLine(nums[2]); // Outputs: 99
}
}
References to local variables are invalid, for example: ref int local = 5; return ref local;
would fail.
What .NET engineers should know:
👼 Junior: ref locals
and ref returns
allow for modifying variables directly through references rather than copying their values.
🎓 Middle: Useful for performance when dealing with large structs; however, they come with restrictions to ensure safety, such as not returning references to local variables.
👑 Senior:
- Advanced scenarios such as using
ref structs
to achieve zero-allocation coding patterns. - The impact of using
ref returns
on garbage collection and memory safety.
📚 Resources:
What is a readonly struct
, and how does it differ from a regular struct?
A readonly struct
in C# explicitly prevents modification after construction by enforcing immutability. It guarantees that all instance members do not modify the state, thereby helping to improve performance and reduce bugs related to accidental state changes.
public readonly struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public int Sum() => X + Y; // Can't modify X or Y
}
Note:
For send a read-only struct as reference into parameters without copying use in parameter void Process(in ReadOnlyStruct data)
What .NET engineers should know about readonly struct
:
- 👼 Junior: should know that a readonly struct cannot modify its state after initialization.
- 🎓 Middle: Helps prevent unintended modifications and can improve performance by avoiding defensive copies.
- 👑 Senior: Understand how readonly structs can optimize performance by enabling efficient use of
in
parameters, which avoids copying large structs. Evaluate when readonly structs are beneficial in performance-critical paths or high-frequency operations.
📚 Resources: Readonly structs
Explain the concept of nullable reference types introduced in C# 8.0.
Nullable reference types, introduced in C# 8.0, enhance code safety by allowing developers to explicitly specify whether a reference type variable may be null, helping to catch potential null-related issues at compile time and reducing runtime NullReferenceException
errors. By default, reference types in C# can be null, which can lead to unexpected errors if not appropriately handled. With nullable reference types enabled, developers can annotate variables to indicate nullability, and the compiler uses static flow analysis to enforce safe usage. Flow analysis improvements in later C# versions (e.g., C# 9.0+) provide more precise warnings, such as detecting nullability in complex control flows, and nullable annotation attributes like AllowNull
and DisallowNull
offer finer control over nullability contracts.
- Non-nullable reference types are declared without annotation, indicating they should not be null. The compiler issues warnings for potential null assignments or dereferences.
string nonNullableString = "Hello";
nonNullableString = null; // Compiler warning: Assigning null to non-nullable type
Nullable reference types are declared with a ?
suffix, indicating they can be null. The compiler enforces null checks before dereferencing.
string? nullableString = "Hello";
nullableString = null; // No warning
if (nullableString != null)
{
Console.WriteLine(nullableString.Length); // Safe dereference
}
Nullable Annotation Attributes: AllowNull
and DisallowNull
refine nullability for advanced scenarios. For example, AllowNull
permits null input for a non-nullable property while DisallowNull
ensuring a nullable parameter cannot be null.
public class Example
{
[AllowNull]
public string NonNullableString { get; set; } = "Default";
public void Process([DisallowNull] string? input)
{
Console.WriteLine(input.Length);
}
}
Enabling Nullable Reference Types:
To enable this feature, add the following to your project’s .csproj file:
<Nullable>enable</Nullable>
What .NET engineers should know:
- 👼 Junior: Should understand the purpose of nullable reference types, how to declare variables as nullable
string?
or non-nullablestring
, and why this helps avoid null errors. - 🎓 Middle: Expected to implement nullable reference types in projects, refactor legacy code to use them, and apply attributes like
AllowNull
,DisallowNull
, orNotNull
to clarify nullability in complex APIs. - 👑 Senior: Should design null-safe systems leveraging advanced flow analysis in C# 9.0+ for precise null checking, use attributes like
AllowNull
andDisallowNull
to enforce robust contracts and guide teams on adopting nullable reference types to improve code reliability and maintainability.
📚 Resources:
What are nullable value types, and how are they used?
Nullable value types (T?
) allow value types (like int
, bool
, DateTime
) to represent an undefined or missing value by assigning null
. This is useful when a value type needs to indicate the absence of a value, such as in database operations or optional parameters.
Example:
int? age = null;
if (age.HasValue)
Console.WriteLine($"Age: {age.Value}");
else
Console.WriteLine("Age not specified.");
What .NET engineers should know:
- 👼 Junior: Use
T?
to represent value types that can benull
, likeint?
orbool?
. - 🎓 Middle: Utilize
.HasValue
and.Value
to check and retrieve values; use the??
operator to provide default values. - 👑 Senior: Understand
Nullable<T>
struct behavior, its integration with generics, and implications for performance and boxing.
📚 Resources
What are generics in C#, and what problems do they solve?
Generics enable the creation of classes, methods, and interfaces with a placeholder for the data type, promoting code reusability, type safety, and performance by eliminating the need for type casting and boxing.
Example:
public class DataStore<T>
{
private T data;
public void SetData(T value) => data = value;
public T GetData() => data;
}
What .NET engineers should know about generics:
- 👼 Junior: Generics let you write code that works with any data type, like
List<T>
. - 🎓 Middle: They prevent runtime errors by ensuring type safety at compile time and reducing code duplication.
- 👑 Senior: Utilize constraints (
where T : class
) to enforce type requirements and understand how generics affect runtime behavior and reflection.
📚 Resources: Generic classes and methods - C# | Microsoft Learn
What are anonymous types, and when would you use them?
Anonymous types are unnamed classes created on the fly using the new
keyword with object initializers. They are typically used for encapsulating a set of read-only properties into a single object without explicitly defining a type, often in LINQ queries.
Example:
var person = new { FirstName = "John", LastName = "Doe" };
Console.WriteLine($"{person.FirstName} {person.LastName}");
What .NET engineers should know:
- 👼 Junior: Use anonymous types to quickly group related data without creating a separate class.
- 🎓 Middle / 👑 Senior: Understand the limitations of anonymous types in terms of type inference and scope. Consider code maintainability and clarity factors when evaluating whether to use anonymous types, named types, or tuples.
What is the difference between struct
and class
in C#?
In C#, a struct
is a value type, while a class
is a reference type. This core difference influences how they are stored in memory, their performance characteristics, and their behavior during assignment and method calls.
Memory Storage
- Structs: As value types, structs are stored directly where they are declared. For local variables, this is typically the stack, but their storage location depends on context. If a struct is a field in a class, an element in an array, or captured in a closure (e.g., in lambdas), it resides on the heap. Additionally, structs can be boxed (wrapped in an object) and stored on the heap when treated as objects (e.g., cast to object or an interface).
- Classes: As reference types, classes are always allocated on the heap, with variables holding references (pointers) to their memory locations.
For example:

Code Example:
struct PointStruct { public int X, Y; }
class PointClass { public int X, Y; }
// Struct example
var struct1 = new PointStruct { X = 1, Y = 2 };
var struct2 = struct1; // Copies the entire struct
struct2.X = 5;
Console.WriteLine(struct1.X); // Outputs: 1 (original unchanged)
// Class example
var class1 = new PointClass { X = 1, Y = 2 };
var class2 = class1; // Copies the reference
class2.X = 5;
Console.WriteLine(class1.X); // Outputs: 5 (both point to the same object)
What .NET engineers should know:
- 👼 Junior: Understand that
struct
is a value type;class
is a reference type. - 🎓 Middle: Understand that
struct
cannot inherit from anotherstruct
orclass
. Be aware that structs are copied when passed to methods (unless the method usesref
orin
), which can impact performance if the struct is large. - 👑 Senior:
- Evaluate performance trade-offs between
struct
andclass
:- Boxing: Structs incur heap allocations and performance overhead when boxed (e.g., cast to object or an interface).
- Memory Layout: Structs in arrays are stored contiguously, which improves cache efficiency, whereas classes involve pointer overhead.
- Garbage Collection: Classes contribute to GC pressure, whereas unboxed structs do not.
- Follow .NET guidelines for using struct:
- Prefer
struct
for small types (typically less than 16 bytes), immutable, and represent single values or lightweight data structures (e.g.,Point
,DateTime
).
- Prefer
- Understand advanced use cases, such as
ref struct
(e.g.,Span<T>
), which enforces stack-only allocation to avoid heap overhead.
- Evaluate performance trade-offs between
📚 Resources:
What are enums, and how do you use them?
An enum
(enumeration) defines a set of named constants, improving code readability and type safety when working with a fixed set of related values
Example:
enum Day { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday }
Day today = Day.Monday;
Console.WriteLine((int)today); // Outputs: 1
What .NET engineers should know:
👼 Junior: Use enums to represent a group of related constants with meaningful names.
🎓 Middle:
- Customize underlying values and types (e.g.,
byte
,short
). - Use
[Flags]
attribute for bitwise operations when combining enum values.
👑 Senior Developer Should Know:
- Implement enums in APIs for clarity and maintainability.
- Understand serialization behavior and versioning considerations with enums.
📚Resources: Enum Design
What are attributes in C#, and how are they applied?
Attributes in C# are metadata annotations that provide additional information about code elements (classes, methods, properties, etc.). They can influence program behavior at runtime or during compilation.
[Obsolete("Use NewMethod instead.")]
public void OldMethod() { }
public void NewMethod() { }
What .NET engineers should know:
- 👼 Junior: Attributes are used to add metadata to code elements. They are enclosed in square brackets and placed above declarations.
- 🎓 Middle: Can create custom attributes by inheriting from
System.Attribute
. Able to retrieve attribute information at runtime using reflection. - 👑 Senior: Utilize attributes for advanced scenarios like dependency injection, serialization, and aspect-oriented programming. Understand how attributes interact with tools and frameworks (e.g., ASP.NET Core, NUnit).
📚Resources:
What is the purpose of the sealed
keyword?
The sealed
keyword in C# is used to prevent a class from being inherited or a method from being overridden, enhancing security and performance.
public sealed class FinalClass
{
public void Display() => Console.WriteLine("FinalClass Display");
}
What .NET engineers should know:
- 👼 Junior Developer Should Know: A
sealed
class cannot be inherited. Asealed
method cannot be overridden in derived classes. - 🎓 Middle: Use
sealed
to prevent unintended inheritance and to enforce design constraints. Understand that sealing classes can lead to performance optimizations - 👑 Senior:Apply
sealed
in scenarios where class behavior must remain unchanged, such as utility or helper classes. Recognize the implications of sealing classes on extensibility and testing.
What are partial types, and what are their benefits?
What .NET engineers should know:
Partial types in C# allow the definition of a class, struct, or interface to be split across multiple files, facilitating better organization and collaboration.
// File1.cs public partial class Employee { public void Display() => Console.WriteLine("Display"); }
// File2.cs public partial class Employee { public void CalculateSalary() => Console.WriteLine("CalculateSalary"); }
👼 Junior : Partial classes enable splitting a class's definition across various files. All parts must use the partial
keyword and be in the same namespace.
📚Resources:
What is the difference between as
and explicit casting?
In C#, both as
and explicit casting are used to convert between types, but they behave differently when the conversion fails.
as
Operator: Attempts to cast an object to a specified type. If the cast fails, it returns null
instead of throwing an exception.
Explicit Casting (Type)
: Attempts to cast an object to a specified type. If the cast fails, it throws an InvalidCastException
.
object obj = "hello";
// Using 'as' operator
string str1 = obj as string; // str1 is "hello"
int? num1 = obj as int?; // num1 is null
// Using explicit casting
string str2 = (string)obj; // str2 is "hello"
int num2 = (int)obj; // Throws InvalidCastException
What .NET engineers should know:
👼 Junior / 🎓 Middle / 👑 Senior:: Understand that as
works only with reference types and nullable types.
What are type constraints in generics?
Type constraints in generics restrict what types can be used with a generic class, method, or interface.
They allow you to enforce requirements like "must be a class," "must have a parameterless constructor," or "must inherit from a specific base type."
Without constraints, you cannot safely use certain operations inside a generic type.
Code Example:
public class Repository<T> where T : class, new()
{
public T CreateInstance()
{
return new T();
}
}
where T : class
→T
must be a reference type.where T : new()
→T
must have a public parameterless constructor.
What .NET Engineers Should Know:
- 👼 Junior: Should understand that constraints ensure generic types behave predictably.
- 🎓 Middle: Should know how to apply multiple constraints (e.g.,
where T : BaseClass, new()
) and why constraints matter in API design. - 👑 Senior: Should design robust and reusable generic libraries using complex constraint combinations like interfaces, base classes, and constructor requirements.
Why is it important to override Equals()
and GetHashCode()
for value types?
By default, value types in C# compare by value, but when you customize equality (e.g., in a struct
used in dictionaries or sets),
You should override Equals()
and GetHashCode()
to ensure correct comparisons and performance.
What .NET Engineers Should Know:
- 👼 Junior: Understand that overriding
Equals()
andGetHashCode()
ensures value types behave correctly when compared or stored in collections. - 🎓 Middle: Should properly implement both methods to avoid bugs in hash-based collections.
- 👑 Senior: Should optimize for performance (e.g., use
HashCode.Combine
) and know special rules when using structs in concurrent or large-scale systems.
📚 Resources:
GetHashCode inside CLR: Value types
What are the benefits and Use Cases of the init
accessor introduced in C# 9.0?
The init
accessor, introduced in C# 9.0, allows properties to be assigned during object initialization but makes them immutable, preventing modifications after construction. This feature simplifies the creation of immutable types by enabling concise object initialization while ensuring data integrity. The init
accessor pairs seamlessly with record types, also introduced in C# 9.0, to support immutable object initialization, promoting robust immutable design patterns where objects behave like values with fixed states after creation.
- This feature enables properties to be set during object creation using object initializers, ensuring immutability post-initialization without requiring complex constructor logic.
- Provides clear, readable object initialization, improving code maintainability compared to constructor-only approaches.
- Enhances record types by allowing flexible initialization of immutable properties, supporting value-based equality and immutable data models critical for reliable software design.
Example
public class Product
{
public string Name { get; init; }
public decimal Price { get; init; }
}
var product = new Product
{
Name = "Laptop",
Price = 999.99m
};
// product.Price = 899.99m; // Compile-time error: Property is init-only
In this example, the Product
class uses init
accessors, allowing Name
and Price
to be set during initialization but preventing changes afterward, ensuring immutability.
Example with Record:
public record ProductRecord(string Name, decimal Price);
var productRecord = new ProductRecord("Laptop", 999.99m);
// productRecord.Price = 899.99m; // Compile-time error: Record properties are init-only
Records use init
accessors by default, combining immutable initialization with value-based equality for concise, robust data models.
Use Cases:
- Immutable Data Models are ideal for defining data models, such as configuration settings or data transfer objects (DTOs), where property values should remain fixed after creation to prevent unintended changes.
- Record Types: This class leverages
init
accessors to initialize immutable records, ensuring thread-safe, predictable behavior in domains like financial systems or API payloads. - Functional Programming Patterns: This pattern supports immutable object design, aligning with functional paradigms that minimize state changes for improved reliability.
What .NET engineers should know:
- 👼 Junior: Should understand how to declare init accessors and use them to create immutable properties, recognizing their role in preventing changes after object creation.
- 🎓 Middle: Expected to apply init accessors in projects, especially with records, to build immutable data models and identify scenarios where immutability improves code safety and clarity.
- 👑 Senior: Should design systems leveraging init accessors and records for immutable, thread-safe designs, guide teams on their use in functional and domain-driven patterns, and optimize initialization workflows for maintainability and performance.
📚 Resources:
What is stackalloc
, and how does it optimize memory allocation?
stackalloc
allocates a block of memory on the stack instead of the heap. Stack memory is much faster to allocate and deallocate compared to heap memory because it follows a simple LIFO (Last-In, First-Out) model.
Code Example:
Span<int> numbers = stackalloc int[5] { 1, 2, 3, 4, 5 };
foreach (var number in numbers)
{
Console.WriteLine(number);
}
What .NET Engineers Should Know about stackalloc
:
- 👼 Junior: Understand that
stackalloc
allocates memory on the stack for performance. - 🎓 Middle: Use
stackalloc
carefully to avoid stack overflows (limit size). - 👑 Senior: Optimize memory-critical paths and know when to avoid heap allocations to improve GC performance.
📚 Resources: stackalloc
keyword
How do with
expressions simplify creating modified copies of immutable objects?
A with
expression allows creating a new object based on an existing one, copying all properties except those you want to change.
✅ Commonly used with records.
Code Example:
public record Person(string Name, int Age);
var person1 = new Person("Alice", 30);
var person2 = person1 with { Age = 31 };
Console.WriteLine(person2); // Output: Person { Name = Alice, Age = 31 }
What .NET Engineers Should Know about with
expressions:
- 👼 Junior: Understand
with
is used for immutability — creating modified copies. - 🎓 Middle: Use
with
in records for clean and safe updates. - 👑 Senior: Design immutable types that integrate well with
with
expressions, ensuring no side effects.
📚 Resources: With Expressions
What is a target-typed new
expression, and when should you use it?
Target-typed new
lets you omit repeating the type on the right-hand side when the type is already known from the left-hand side.
Introduced in C# 9.0 to reduce redundancy.
You can use:
Person person = new("Alice", 30);
Instead of writing:
Person person = new Person("Alice", 30);
📚 Resources:
What is the fixed
statement in C#, and when would you use it?
The fixed
statement pins an object in memory, preventing the garbage collector from moving it. It’s used when you need to work with unsafe code, like interacting with native APIs or pointers.
unsafe
{
int[] numbers = { 1, 2, 3 };
fixed (int* p = numbers)
{
Console.WriteLine(*p); // Outputs: 1
}
}
What .NET Engineers Should Know about fixed
:
- 👼 Junior: Understand
fixed
prevents GC from moving objects during unsafe operations. - 🎓 Middle: Use
fixed
safely when working with pointers or interop scenarios. - 👑 Senior: Optimize interop boundaries and ensure no memory leaks or dangling pointers.
📚 Resources:
How can you suppress nullable warnings using the null-forgiving operator !
?
The null-forgiving operator (!
) tells the compiler "I know this value isn't null" even if its type says it could be.
Example:
string? name = GetName();
Console.WriteLine(name!.Length); // Tells compiler "trust me, name isn't null"
📚 Resources: Null-forgiving Operator
What is the difference between float
, double
, and decimal
in C#?
In C#, float
, double
, and decimal
are types for representing numbers with fractions (non-integers), but they serve different precision and use cases:
Type | Size | Precision | Common Use Cases |
---|---|---|---|
float | 32-bit | ~6-9 digits | Graphics, gaming, approximate math |
double | 64-bit | ~15-17 digits | Scientific calculations, default floating-point |
decimal | 128-bit | ~28-29 digits, base-10 | Financial and monetary calculations |
Key Difference:
float
anddouble
use binary floating-point IEEE 754 standard — may introduce tiny rounding errors.decimal
uses base-10 — more precise for financial and monetary data, but slower in performance.
float floatValue = 1.1234567f;
double doubleValue = 1.123456789012345;
decimal decimalValue = 1.1234567890123456789012345m;
Console.WriteLine(floatValue); // Less precise
Console.WriteLine(doubleValue); // More precise
Console.WriteLine(decimalValue); // High precision for monetary
What .NET Engineers Should Know:
- 👼 Junior: Know that
float
,double
, anddecimal
have different precision levels. Understanddecimal
is better for money to avoid rounding errors. - 🎓 Middle: Choose
float
for performance-critical apps (e.g., 3D games),double
for general math, anddecimal
for financial calculations. - 👑 Senior: Understand internal representations, performance impacts, and trade-offs between binary vs decimal precision. Be able to explain why base-10 math matters for banking software but is unnecessary for other use-cases.
📚 Resources:
What are nint
and nuint
types in C#, and when are they useful?
nint
(native int) and nuint
(native unsigned int) are integer types whose size depends on the platform:
- 32-bit on 32-bit systems
- 64-bit on 64-bit systems
They are useful for interop, pointer math, and performance-sensitive native operations.
Code Example:
nint ptrOffset = 42;
nuint size = 100u;
What .NET Engineers Should Know about nint
/nuint
:
- 👼 Junior: Understand that
nint
/nuint
are platform-sized integers. - 🎓 Middle: Use when dealing with low-level memory addresses or native APIs.
- 👑 Senior: Handle cross-platform scenarios correctly and optimize pointer-heavy code safely.
📚 Resources: nint
and nuint
types
What is a ref struct
in C#, and why are they restricted to the stack?
A ref struct
is a structure that must live on the stack, not the heap.
They are designed for high-performance scenarios where memory safety and no garbage collection (GC) pressure are required.
Examples of structures: Span<T>
, ReadOnlySpan<T>
Code Example:
ref struct MyRefStruct
{
public int Value;
}
nint
- Stack memory is short-lived and predictable.
- Prevents dangerous heap escapes that could crash the runtime (e.g., if
Span<T>
were captured incorrectly).
What .NET Engineers Should Know about ref struct
:
- 👼 Junior: Understand
ref struct
cannot be stored in fields, captured in lambdas, or boxed. - 🎓 Middle: Use
ref struct
types likeSpan<T>
for memory-efficient operations. - 👑 Senior: Design APIs safely with
ref struct
, avoiding lifetime issues and enforcing safe memory patterns.
📚 Resources: ref structure types
What is the purpose of the unmanaged
constraint in generics?
The unmanaged
constraint ensures that a generic type parameter is a blittable type — a type that can be copied directly in memory without transformation. Useful for interop, unsafe code, and low-level memory operations.
Example:
public void WriteToBuffer<T>(T value) where T : unmanaged
{
// Safe to use in pointer contexts
}
What .NET Engineers Should Know about unmanaged
:
- 👼 Junior: Know that
unmanaged
means no reference types inside. - 🎓 Middle: Use
unmanaged
to work safely with memory blocks and native code. - 👑 Senior: Able to design high-performance generic libraries that avoid runtime overhead.
📚 Resources: where (generic type constraint) (C# Reference)
What are function pointers (delegate*
) in C#, and when would you use them?
Function pointers (delegate*
) allow you to call functions via raw pointers instead of using Delegate
objects. Introduced in C# 9 for maximum performance and interop.
Code Example (unsafe):
unsafe
{
delegate*<int, int, int> add = &amp;amp;amp;amp;amp;amp;amp;amp;amp;Add;
int result = add(2, 3);
}
static int Add(int a, int b) => a + b;
What .NET Engineers Should Know about delegate*
:
- 👼 Junior: Understand that function pointers allow calling methods through a pointer.
- 🎓 Middle: Use
delegate*
when extremely low-overhead calls are needed, such as in high-frequency systems. - 👑 Senior: Handle calling conventions (
stdcall
,cdecl
) and ensure safety around raw pointers.
📚 Resources: Function Pointers