0% found this document useful (0 votes)
123 views103 pages

Csharp Net Interview Compendium Sample

Бесплатная ознакомительная версия с книгой

Uploaded by

hugodoser
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
123 views103 pages

Csharp Net Interview Compendium Sample

Бесплатная ознакомительная версия с книгой

Uploaded by

hugodoser
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 103

Preface

“An investment in knowledge always pays the best interest.”


Benjamin Franklin

In today’s highly competitive job market, standing out as a programmer requires more than
just experience — it demands confidence, a strong grasp of the fundamentals, and the ability to
demonstrate practical skills under pressure. Whether you’re a recent graduate preparing for your
first technical interview or a seasoned developer looking to brush up on modern C# and .NET
concepts, this book is designed to guide you every step of the way.

This compendium is a carefully structured resource that blends theory with hands-on prac-
tice. It delves into the essential knowledge areas you’ll encounter in technical interviews, including
object-oriented programming, advanced features of C#, and the core building blocks of the .NET
ecosystem. Through a combination of clear explanations, best practices, and fully functional code
examples, you’ll build both understanding and muscle memory.

What sets this book apart is its focus on interview readiness. It doesn’t just explain concepts —
it anticipates the types of questions you’ll be asked and demonstrates how to approach them with
confidence. You’ll explore real-world scenarios, common interview problems, and coding exercises
that reinforce your understanding in a practical, applicable way.

In a time where economic uncertainty makes job hunting even more challenging, being prepared
isn’t optional — it’s essential. This book equips you with the tools and mindset needed to succeed
in technical interviews and land the job you deserve.

Let this book be your roadmap. Study it, practice with it, and carry its lessons with you into
your next interview — and beyond.

Yohan J. Rodríguez

i
Contents

Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i

1 C# Fundamentals 1
1.1 C# Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Delegates and Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.3 Value Types, Reference Types, Immutable, Semantics . . . . . . . . . . . . . . . . . 22
1.4 Types and Type Differences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
1.5 Collections and LINQ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

2 OOP Design and Best Practices 56


2.1 Object-Oriented Programming (OOP) . . . . . . . . . . . . . . . . . . . . . . . . . 56
2.2 SOLID Principles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
2.3 DRY (Don’t Repeat Yourself) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
2.4 Clean Code and Best Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
2.5 Incremental Refactor and Code Update Techniques . . . . . . . . . . . . . . . . . . 87

ii
1 | C# Fundamentals

Domain of C# fundamentals is a pivotal factor in securing a role within the .NET ecosystem.
During technical interviews, employers frequently assess candidates on their comprehension of
core language features, including data types, operators, control flow, and object-oriented princi-
ples. Demonstrating proficiency in these areas not only indicates a solid foundation but also signals
the capacity to learn and integrate more advanced concepts.

A thorough understanding of these key elements is particularly valuable in today’s competitive


job market, where coding assessments and conceptual discussions have become standard. By
fortifying your expertise in C# fundamentals, you position yourself to navigate a variety of technical
challenges confidently, display adept problem-solving skills, and ultimately distinguish yourself as
a strong candidate in any interview setting.

1.1 C# Basics
What is the difference between ‘const‘ and ‘readonly‘ in C#, and when
would you use each?
‘const‘ is a compile-time constant, meaning its value is fixed and known at compile-time and can-
not be modified. It must be initialized at the time of declaration. On the other hand, ‘readonly‘
is a runtime constant, which can be assigned either at the time of declaration or within a con-
structor. ‘readonly‘ allows you to set values dynamically during object instantiation but prevents
modification after that.

Listing 1.1: Code example

1 public class Constants


2 {
3 public const int MaxSize = 100; // Must be assigned at compile time
4 public readonly int MinSize ; // Can be assigned at runtime
5

6 public Constants ( int minSize )


7 {

1
1.1. C# Basics

8 MinSize = minSize ; // Allowed in constructor


9 }
10 }

What is the difference between ‘abstract‘ classes and ‘interfaces‘ in C#,


and when would you use each?
An ‘abstract‘ class can provide both implementation and abstract members (methods or properties
without implementation), while an ‘interface‘ can only define method signatures (starting with C#
8.0, interfaces can also have default implementations). You use an abstract class when classes share
common behavior, but you want to force them to implement certain methods. Use an interface
when you want to define a contract for behavior without prescribing any specific implementation.

Listing 1.2: Code example

1 // Abstract class with a method implementation


2 public abstract class Shape
3 {
4 public abstract double Area () ; // Abstract method
5 public void Display () = > Console . WriteLine ( " I am a shape " ) ;
6 }
7

8 // Interface with method signature


9 public interface IShape
10 {
11 double Area () ;
12 }
13

14 // Class that extends an abstract class


15 public class Circle : Shape
16 {
17 public double Radius { get ; set ; }
18 public override double Area () = > Math . PI * Radius * Radius ;
19 }
20

21 // Class that implements an interface


22 public class Rectangle : IShape
23 {
24 public double Length { get ; set ; }
25 public double Width { get ; set ; }
26 public double Area () = > Length * Width ;
27 }

2
1.1. C# Basics

What are extension methods in C# and how do they work?


Extension methods allow you to add new methods to existing types without modifying the original
type or creating a new derived type. Extension methods are defined as static methods in a static
class, and they use the ‘this‘ keyword in their first parameter to specify the type they extend.

Listing 1.3: Code example

1 public static class StringExtensions


2 {
3 // Extension method for the string class
4 public static bool IsNullOrEmpty ( this string str )
5 {
6 return string . IsNullOrEmpty ( str ) ;
7 }
8 }
9

10 class Program
11 {
12 static void Main ()
13 {
14 string message = " Hello , World ! " ;
15 bool result = message . IsNullOrEmpty () ; // Using the extension method
16 Console . WriteLine ( result ) ; // Outputs False
17 }
18 }

What is the purpose of the ‘lock‘ statement in C#, and how does it
prevent threading issues?
The ‘lock‘ statement in C# is used to ensure that a block of code is executed by only one thread at
a time. It helps prevent **race conditions** by ensuring that only one thread can access the locked
section of code. ‘lock‘ is typically used in scenarios where shared resources need to be accessed by
multiple threads simultaneously.

Listing 1.4: Code example

1 public class Counter


2 {
3 private int _count = 0;
4 private readonly object _lock = new object () ;
5

6 public void Increment ()


7 {
8 // Only one thread can execute this block at a time
9 lock ( _lock )

3
1.1. C# Basics

10 {
11 _count ++;
12 }
13 }
14

15 public int GetCount ()


16 {
17 return _count ;
18 }
19 }

What is covariance and contravariance in C#, and how do they apply to


generics?
Covariance allows a method to return a more derived type than originally specified, while con-
travariance allows a method to accept parameters that are less derived than originally specified. In
C#, covariance and contravariance are used with generics, delegates, and interfaces. They allow
you to implicitly convert types in inheritance hierarchies.

Listing 1.5: Code example

1 // Covariance : Returning a derived type


2 public interface ICovariant < out T > { }
3 public class Animal { }
4 public class Dog : Animal { }
5

6 // Contravariance : Accepting a base type


7 public interface IContravariant < in T > { }
8

9 class Program
10 {
11 static void Main ()
12 {
13 // Covariance example : ICovariant < Animal > can hold ICovariant < Dog >
14 ICovariant < Animal > animals = new ICovariant < Dog >() ;
15

16 // Contravariance example : IContravariant < Dog > can hold IContravariant <
Animal >
17 IContravariant < Dog > dogs = new IContravariant < Animal >() ;
18 }
19 }

4
1.1. C# Basics

What are expression-bodied members in C# and when would you use


them?
Expression-bodied members allow concise syntax for methods, properties, and other members when
the implementation is a single expression. They simplify the code and enhance readability.

Listing 1.6: Code example

1 public class Circle


2 {
3 public double Radius { get ; set ; }
4

5 // Expression - bodied property


6 public double Area = > Math . PI * Radius * Radius ;
7

8 // Expression - bodied method


9 public double Circumference () = > 2 * Math . PI * Radius ;
10 }

What is the difference between ‘Task‘ and ‘Thread‘ in C#?


A ‘Task‘ represents an asynchronous operation and is part of the **Task Parallel Library (TPL)**.
Tasks are higher-level abstractions over threads and provide better control over asynchronous code
execution. A ‘Thread‘, on the other hand, represents an individual thread of execution. Tasks
are preferred for background work, as they are more efficient and offer more features such as
cancellation and continuation.

Listing 1.7: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 // Using Task for asynchronous work
6 Task . Run (() = > DoWork () ) ;
7

8 // Using Thread for lower - level threading


9 Thread thread = new Thread ( DoWork ) ;
10 thread . Start () ;
11 }
12

13 static void DoWork ()


14 {
15 Console . WriteLine ( " Work is being done . " ) ;
16 }
17 }

5
1.1. C# Basics

What are async and await in C#, and how do they improve the respon-
siveness of your application?
‘async‘ and ‘await‘ are used to write asynchronous code in a more readable, sequential manner.
They allow you to run code asynchronously without blocking the main thread, improving the
responsiveness of applications, particularly in GUI and web applications.

Listing 1.8: Code example

1 public async Task < int > FetchDataAsync ()


2 {
3 // Asynchronous call to simulate data fetching
4 await Task . Delay (2000) ;
5 return 42;
6 }
7

8 class Program
9 {
10 static async Task Main ( string [] args )
11 {
12 Console . WriteLine ( " Fetching data ... " ) ;
13 int result = await FetchDataAsync () ;
14 Console . WriteLine ( $ " Data fetched : { result } " ) ;
15 }
16 }

What are delegates in C#, and how do they differ from events?
A delegate is a type that represents references to methods with a specific signature. Delegates are
used to define callback methods. Events are a special kind of delegate that are typically used for
notifications. Events provide a layer of abstraction that restricts the ability to invoke the delegate
from outside the class where the event is defined.

Listing 1.9: Code example

1 // Delegate definition
2 public delegate void Notify () ;
3

4 // Publisher class
5 public class ProcessBusinessLogic
6 {
7 public event Notify ProcessCompleted ;
8

9 public void StartProcess ()


10 {
11 Console . WriteLine ( " Process started . " ) ;

6
1.1. C# Basics

12 // Raise event
13 OnProcessCompleted () ;
14 }
15

16 protected virtual void OnProcessCompleted ()


17 {
18 ProcessCompleted ?. Invoke () ;
19 }
20 }
21

22 class Program
23 {
24 static void Main ( string [] args )
25 {
26 ProcessBusinessLogic bl = new ProcessBusinessLogic () ;
27 bl . ProcessCompleted += () = > Console . WriteLine ( " Process completed . " ) ;
28 bl . StartProcess () ;
29 }
30 }

What is ‘IDisposable‘ and the purpose of the ‘using‘ statement in C#?


‘IDisposable‘ is an interface that provides a mechanism for releasing unmanaged resources. The
‘using‘ statement ensures that ‘Dispose‘ is called on an object that implements ‘IDisposable‘,
typically to release file handles, database connections, or network resources. The ‘using‘ block
automatically calls ‘Dispose‘ at the end of the block.
Listing 1.10: Code example

1 public class Resource : IDisposable


2 {
3 public void Dispose ()
4 {
5 Console . WriteLine ( " Resources have been released . " ) ;
6 }
7 }
8

9 class Program
10 {
11 static void Main ()
12 {
13 using ( var resource = new Resource () )
14 {
15 // Work with the resource
16 } // Automatically calls Dispose here
17 }
18 }

7
1.1. C# Basics

What is the difference between ‘IEnumerable‘ and ‘IQueryable‘ in C#?


‘IEnumerable‘ is used for in-memory collection manipulation and deferred execution. It pro-
cesses data in-memory and is best for working with data already in memory, like lists or arrays.
‘IQueryable‘ is used for querying data from external sources like databases, where query execution
happens at the data source. It supports efficient query translation, like converting LINQ to SQL.
Listing 1.11: Code example

1 IEnumerable < int > numbers = new List < int > { 1 , 2 , 3 , 4 };
2 var evenNumbers = numbers . Where ( n = > n % 2 == 0) ; // In - memory filtering
3

4 IQueryable < int > queryableNumbers = numbers . AsQueryable () ;


5 var queryableEvenNumbers = queryableNumbers . Where ( n = > n % 2 == 0) ; // Potentially
optimized filtering

What is ‘Dependency Injection‘ in C#, and how is it implemented?


Dependency Injection (DI) is a design pattern used to reduce tight coupling between classes by
providing dependencies from the outside. In C#, DI is commonly implemented using frameworks
like ASP.NET Core’s built-in DI container. You register services with the DI container and inject
them into classes that depend on them via constructors or methods.
Listing 1.12: Code example

1 public interface ILogger


2 {
3 void Log ( string message ) ;
4 }
5

6 public class ConsoleLogger : ILogger


7 {
8 public void Log ( string message ) = > Console . WriteLine ( message ) ;
9 }
10

11 public class Application


12 {
13 private readonly ILogger _logger ;
14

15 public Application ( ILogger logger ) = > _logger = logger ;


16

17 public void Run () = > _logger . Log ( " Application running . " ) ;
18 }
19

20 // Registering and resolving dependencies using a DI container in ASP . NET Core


21 // services . AddSingleton < ILogger , ConsoleLogger >() ;
22 // services . AddTransient < Application >() ;

8
1.1. C# Basics

What is the difference between ‘finalize‘ and ‘dispose‘ in C#?


‘Dispose‘ is a method from the ‘IDisposable‘ interface used to release unmanaged resources man-
ually and deterministically. ‘Finalize‘ (or a destructor) is used by the garbage collector to release
unmanaged resources when the object is being collected. ‘Dispose‘ should be used for explicit
resource management, while ‘Finalize‘ is a safety net.

Listing 1.13: Code example

1 public class Resource : IDisposable


2 {
3 // Dispose for explicit resource management
4 public void Dispose ()
5 {
6 // Clean up resources
7 }
8

9 // Destructor as a fallback cleanup


10 ~ Resource ()
11 {
12 Dispose () ;
13 }
14 }

What is the ‘volatile‘ keyword in C#, and when would you use it?
The ‘volatile‘ keyword is used on fields that are accessed by multiple threads. It ensures that the
most up-to-date value of the variable is always read by the threads, preventing optimizations that
might cause stale values to be used.

Listing 1.14: Code example

1 public class Worker


2 {
3 private volatile bool _isRunning = true ;
4

5 public void Stop ()


6 {
7 _isRunning = false ;
8 }
9

10 public void DoWork ()


11 {
12 while ( _isRunning )
13 {
14 // Perform some work
15 }

9
1.1. C# Basics

16 }
17 }

What are ‘Func‘, ‘Action‘, and ‘Predicate‘ delegates in C#?


• Func<T, TResult> is a delegate that takes parameters and returns a value.
• Action<T> is a delegate that takes parameters but returns nothing.
• Predicate<T> is a delegate that returns a boolean and takes a single parameter.

Listing 1.15: Code example

1 Func < int , int , int > add = (a , b ) = > a + b ; // Takes two integers and returns an
integer
2 Action < string > print = message = > Console . WriteLine ( message ) ; // Takes a string
and returns nothing
3 Predicate < int > isEven = number = > number % 2 == 0; // Returns true if the number
is even

What are asynchronous streams in C# and how do they work?


Asynchronous streams, introduced in C# 8.0, allow you to iterate over asynchronous data sources
using ‘await foreach‘. They are useful when data is being fetched or generated asynchronously and
you want to process items as they become available.

Listing 1.16: Code example

1 public async IAsyncEnumerable < int > GetNumbersAsync ()


2 {
3 for ( int i = 0; i < 5; i ++)
4 {
5 await Task . Delay (1000) ; // Simulating async work
6 yield return i ;
7 }
8 }
9

10 public async Task ProcessNumbersAsync ()


11 {
12 await foreach ( var number in GetNumbersAsync () )
13 {
14 Console . WriteLine ( number ) ; // Outputs numbers as they are produced
15 }
16 }

10
1.1. C# Basics

What is ‘boxing‘ and ‘unboxing‘ in C#? Why is it important?


Boxing is the process of converting a value type (e.g., int) to an object type. Unboxing is the
reverse, where an object is cast back to a value type. Boxing and unboxing introduce performance
overhead because boxing allocates memory on the heap and unboxing requires type casting.
Listing 1.17: Code example

1 int value = 42;


2 object boxedValue = value ; // Boxing : storing value type in object ( heap )
3 int unboxedValue = ( int ) boxedValue ; // Unboxing : converting back to value type

How does the ‘async‘/‘await‘ mechanism handle exceptions in C#?


When using ‘async‘/‘await‘, exceptions thrown during asynchronous code execution are captured
and re-thrown when ‘await‘ is called. You can handle exceptions using standard ‘try-catch‘ blocks
around ‘await‘ statements.
Listing 1.18: Code example

1 public async Task FetchDataAsync ()


2 {
3 try
4 {
5 await Task . Run (() = > throw new InvalidOperationException ( " An error
occurred " ) ) ;
6 }
7 catch ( InvalidOperationException ex )
8 {
9 Console . WriteLine ( $ " Exception caught : { ex . Message } " ) ;
10 }
11 }

What are tuples in C#, and how are they used?


Tuples in C# are a lightweight data structure used to group multiple values. C# 7.0 introduced
**value tuples**, which are mutable and allow naming of fields.
Listing 1.19: Code example

1 public ( int , string ) GetPerson ()


2 {
3 return (1 , " John Doe " ) ; // Returning a tuple
4 }
5

6 var person = GetPerson () ;


7 Console . WriteLine ( $ " ID : { person . Item1 } , Name : { person . Item2 } " ) ;

11
1.2. Delegates and Parameters

What is the difference between ‘Thread.Sleep‘ and ‘Task.Delay‘ in C#?


‘Thread.Sleep‘ blocks the current thread for a specified duration, freezing the execution of the
thread. ‘Task.Delay‘ asynchronously waits for a specified duration without blocking the current
thread, allowing other tasks to run in the meantime. ‘Task.Delay‘ is preferable in asynchronous
programming to avoid blocking resources.

Listing 1.20: Code example

1 // Thread . Sleep blocks the current thread


2 Thread . Sleep (1000) ; // Blocks for 1 second
3

4 // Task . Delay allows other tasks to run while waiting


5 await Task . Delay (1000) ; // Non - blocking 1 - second delay in async code

1.2 Delegates and Parameters


What is the difference between a delegate and an event in C#?
A delegate is a type that represents references to methods with a specific signature, while an
event is a special form of delegate used to provide notifications. Delegates allow direct invocation,
whereas events restrict method invocation to the class that declares the event, thus providing
encapsulation and safety.

Listing 1.21: Code example

1 // Delegate declaration
2 public delegate void Notify () ;
3

4 // Publisher class
5 public class Process
6 {
7 // Event declaration using delegate
8 public event Notify ProcessCompleted ;
9

10 public void StartProcess ()


11 {
12 Console . WriteLine ( " Process started . " ) ;
13 OnProcessCompleted () ;
14 }
15

16 protected virtual void OnProcessCompleted ()


17 {
18 // Invoking the event
19 ProcessCompleted ?. Invoke () ;
20 }

12
1.2. Delegates and Parameters

21 }
22

23 class Program
24 {
25 static void Main ( string [] args )
26 {
27 Process process = new Process () ;
28 process . ProcessCompleted += () = > Console . WriteLine ( " Process completed ! " ) ;
29 process . StartProcess () ; // Outputs " Process started ." followed by " Process
completed !"
30 }
31 }

How do multicast delegates work in C# and what are the potential pit-
falls?
Multicast delegates can reference multiple methods. When invoked, all the methods are called in
order. If any method in the invocation list throws an exception, the remaining methods are not
called. Also, return values from methods in the delegate chain, except for the last one, are ignored.

Listing 1.22: Code example

1 public delegate void Notify () ;


2

3 // Example of multicast delegate


4 class Program
5 {
6 public static void Method1 () = > Console . WriteLine ( " Method1 executed " ) ;
7 public static void Method2 () = > Console . WriteLine ( " Method2 executed " ) ;
8

9 static void Main ()


10 {
11 Notify notify = Method1 ;
12 notify += Method2 ;
13

14 // Invokes Method1 and Method2


15 notify . Invoke () ; // Outputs : Method1 executed , Method2 executed
16 }
17 }

How can you pass a delegate as a parameter to a method in C#?


A delegate can be passed as a parameter to a method in C#. This allows the method to receive
and invoke the delegate at runtime, which provides flexibility in method execution.

13
1.2. Delegates and Parameters

Listing 1.23: Code example

1 public delegate void ProcessDelegate ( int x ) ;


2

3 class Program
4 {
5 // Method accepting delegate as parameter
6 public static void Execute ( ProcessDelegate process , int value )
7 {
8 process . Invoke ( value ) ;
9 }
10

11 public static void Print ( int x ) = > Console . WriteLine ( $ " Value : { x } " ) ;
12

13 static void Main ()


14 {
15 ProcessDelegate del = Print ;
16 Execute ( del , 5) ; // Outputs : Value : 5
17 }
18 }

What are anonymous methods in C# and how do they relate to dele-


gates?
Anonymous methods provide a way to define a delegate inline, without the need for a separate
method declaration. They are defined using the ‘delegate‘ keyword and can access variables in the
surrounding scope.

Listing 1.24: Code example

1 public delegate void ProcessDelegate ( int x ) ;


2

3 class Program
4 {
5 static void Main ()
6 {
7 // Anonymous method assigned to delegate
8 ProcessDelegate del = delegate ( int x ) {
9 Console . WriteLine ( $ " Anonymous method executed with value : { x } " ) ;
10 };
11

12 del . Invoke (10) ; // Outputs : Anonymous method executed with value : 10


13 }
14 }

14
1.2. Delegates and Parameters

What is a callback in C#, and how is it implemented using delegates?


A callback is a method passed as a delegate to another method, which will invoke the delegate
after completing its work. This is a common pattern for asynchronous or deferred execution in
C#.

Listing 1.25: Code example

1 public delegate void CallbackDelegate ( int result ) ;


2

3 class Program
4 {
5 // Method that takes a callback delegate
6 public static void PerformCalculation ( int a , int b , CallbackDelegate callback )
7 {
8 int result = a + b ;
9 callback ( result ) ; // Invoking the callback with the result
10 }
11

12 static void Main ()


13 {
14 PerformCalculation (3 , 4 , result = > Console . WriteLine ( $ " Callback executed
with result : { result } " ) ) ;
15 // Outputs : Callback executed with result : 7
16 }
17 }

What are covariance and contravariance in relation to delegates in C#?


Covariance allows a delegate to return a more derived type than specified, while contravariance
allows a delegate to accept parameters that are less derived than specified. These are used to
maintain flexibility when working with inheritance and delegates.

Listing 1.26: Code example

1 public class Animal { }


2 public class Dog : Animal { }
3

4 public delegate Animal AnimalHandler () ;


5 public delegate void DogHandler ( Dog dog ) ;
6

7 class Program
8 {
9 static Animal HandleAnimal () = > new Dog () ; // Covariance : returning a derived
type
10 static void HandleDog ( Animal animal ) { /* Contravariance : accepting base type
*/ }

15
1.2. Delegates and Parameters

11

12 static void Main ()


13 {
14 AnimalHandler animalDelegate = HandleAnimal ; // Covariance
15 DogHandler dogDelegate = HandleDog ; // Contravariance
16 }
17 }

How do you implement a generic delegate in C#?


Generic delegates allow you to define a delegate where the parameter and return types can vary,
providing flexibility for different data types without duplicating code.
Listing 1.27: Code example

1 public delegate T Process <T >( T input ) ;


2

3 class Program
4 {
5 public static int Square ( int x ) = > x * x ;
6 public static string Reverse ( string s ) = > new string ( s . Reverse () . ToArray () ) ;
7

8 static void Main ()


9 {
10 Process < int > processInt = Square ;
11 Process < string > processString = Reverse ;
12

13 Console . WriteLine ( processInt (5) ) ; // Outputs : 25


14 Console . WriteLine ( processString ( " hello " ) ) ; // Outputs : " olleh "
15 }
16 }

How does the ‘Action‘ delegate work in C#, and how is it used?
The ‘Action‘ delegate represents a method that takes one or more input parameters but does not
return a value. It is commonly used for methods that perform actions or side effects.
Listing 1.28: Code example

1 class Program
2 {
3 static void PrintMessage ( string message ) = > Console . WriteLine ( message ) ;
4

5 static void Main ()


6 {
7 Action < string > action = PrintMessage ;
8 action . Invoke ( " Hello , World ! " ) ; // Outputs : Hello , World !

16
1.2. Delegates and Parameters

9 }
10 }

How does the ‘Func‘ delegate work in C#, and how is it used?
The ‘Func‘ delegate represents a method that takes input parameters and returns a value. It is
used when you need to pass a method that both accepts arguments and returns a value.
Listing 1.29: Code example

1 class Program
2 {
3 static int Square ( int x ) = > x * x ;
4

5 static void Main ()


6 {
7 Func < int , int > func = Square ;
8 int result = func . Invoke (5) ; // Outputs : 25
9 Console . WriteLine ( result ) ;
10 }
11 }

How does the ‘Predicate‘ delegate work in C#, and how is it used?
The ‘Predicate‘ delegate represents a method that takes a single parameter and returns a boolean.
It is typically used for filtering or evaluating conditions in collections.
Listing 1.30: Code example

1 class Program
2 {
3 static bool IsEven ( int number ) = > number % 2 == 0;
4

5 static void Main ()


6 {
7 Predicate < int > predicate = IsEven ;
8 bool result = predicate . Invoke (4) ; // Outputs : True
9 Console . WriteLine ( result ) ;
10 }
11 }

How can you combine multiple delegates into a single delegate invoca-
tion?
Delegates can be combined using the ‘+‘ operator to create a multicast delegate. When invoked,
all methods in the invocation list are executed.

17
1.2. Delegates and Parameters

Listing 1.31: Code example

1 public delegate void Notify () ;


2

3 class Program
4 {
5 static void Method1 () = > Console . WriteLine ( " Method1 executed " ) ;
6 static void Method2 () = > Console . WriteLine ( " Method2 executed " ) ;
7

8 static void Main ()


9 {
10 Notify notify = Method1 ;
11 notify += Method2 ;
12

13 notify . Invoke () ; // Outputs : Method1 executed , Method2 executed


14 }
15 }

How do you create an event using a delegate in C#, and how do you
subscribe to it?
You can create an event using a delegate by declaring the event with the delegate type. Clients
can then subscribe to the event using the ‘+=‘ operator and unsubscribe using the ‘-=‘ operator.

Listing 1.32: Code example

1 public delegate void ProcessCompleted () ;


2

3 class Process
4 {
5 public event ProcessCompleted OnProcessCompleted ;
6

7 public void StartProcess ()


8 {
9 // Simulating work
10 OnProcessCompleted ?. Invoke () ; // Trigger the event
11 }
12 }
13

14 class Program
15 {
16 static void Main ()
17 {
18 Process process = new Process () ;
19 process . OnProcessCompleted += () = > Console . WriteLine ( " Process completed ! "
);
20 process . StartProcess () ; // Outputs : Process completed !

18
1.2. Delegates and Parameters

21 }
22 }

How can you use a lambda expression with a delegate in C#?


Lambda expressions provide a concise syntax for creating delegates inline. They can be used
wherever a delegate is required, offering a compact way to define methods.

Listing 1.33: Code example

1 public delegate int Calculate ( int x , int y ) ;


2

3 class Program
4 {
5 static void Main ()
6 {
7 // Lambda expression for addition
8 Calculate add = (x , y ) = > x + y ;
9 Console . WriteLine ( add (3 , 4) ) ; // Outputs : 7
10 }
11 }

How does the ‘params‘ keyword work when passing parameters in C#?
The ‘params‘ keyword allows you to pass a variable number of arguments to a method. The
method treats the ‘params‘ parameter as an array, but you can pass individual arguments without
explicitly creating an array.

Listing 1.34: Code example

1 class Program
2 {
3 static void PrintNumbers ( params int [] numbers )
4 {
5 foreach ( int number in numbers )
6 {
7 Console . WriteLine ( number ) ;
8 }
9 }
10

11 static void Main ()


12 {
13 PrintNumbers (1 , 2 , 3 , 4 , 5) ; // Outputs : 1 , 2 , 3 , 4 , 5
14 }
15 }

19
1.2. Delegates and Parameters

How can you use the ‘ref‘ and ‘out‘ keywords to modify parameters in
C#?
The ‘ref‘ keyword allows a method to modify the value of a parameter passed by reference. The
‘out‘ keyword also allows modification but requires the parameter to be assigned within the method
before use.

Listing 1.35: Code example

1 class Program
2 {
3 static void ModifyValue ( ref int value )
4 {
5 value = 20; // Modifying the reference
6 }
7

8 static void InitializeValue ( out int value )


9 {
10 value = 30; // Must assign a value before exiting
11 }
12

13 static void Main ()


14 {
15 int number = 10;
16 ModifyValue ( ref number ) ;
17 Console . WriteLine ( number ) ; // Outputs : 20
18

19 InitializeValue ( out number ) ;


20 Console . WriteLine ( number ) ; // Outputs : 30
21 }
22 }

How do you implement a callback using delegates for asynchronous meth-


ods in C#?
A delegate can be used as a callback for asynchronous operations. After the operation completes,
the callback delegate is invoked to handle the result.

Listing 1.36: Code example

1 public delegate void CallbackDelegate ( int result ) ;


2

3 class Program
4 {
5 public static void PerformCalculationAsync ( int a , int b , CallbackDelegate
callback )

20
1.2. Delegates and Parameters

6 {
7 Task . Run (() = >
8 {
9 int result = a + b ;
10 callback ( result ) ; // Invoke callback after calculation
11 }) ;
12 }
13

14 static void Main ()


15 {
16 PerformCalculationAsync (3 , 4 , result = > Console . WriteLine ( $ " Result : {
result } " ) ) ; // Outputs : Result : 7
17 }
18 }

What are named and optional parameters in C#, and how do they work?
Named parameters allow you to specify the value of a specific parameter by name rather than
by position. Optional parameters provide default values if arguments are not passed, allowing
methods to be called with fewer parameters than declared.

Listing 1.37: Code example

1 class Program
2 {
3 static void PrintMessage ( string message , string prefix = " Info " )
4 {
5 Console . WriteLine ( $ " { prefix }: { message } " ) ;
6 }
7

8 static void Main ()


9 {
10 // Using named parameters
11 PrintMessage ( message : " Operation completed " , prefix : " Success " ) ;
12 // Using optional parameter
13 PrintMessage ( " Default message " ) ; // Prefix defaults to " Info "
14 }
15 }

How do you handle multiple parameters in delegates using tuple types


in C#?
You can use tuples to pass multiple parameters through a delegate. This approach simplifies
passing multiple values without needing to create a custom class or struct.

21
1.3. Value Types, Reference Types, Immutable, Semantics

Listing 1.38: Code example

1 public delegate void ProcessDelegate (( int , int ) data ) ;


2

3 class Program
4 {
5 static void PrintValues (( int a , int b ) data )
6 {
7 Console . WriteLine ( $ " a : { data . a } , b : { data . b } " ) ;
8 }
9

10 static void Main ()


11 {
12 ProcessDelegate del = PrintValues ;
13 del . Invoke ((5 , 10) ) ; // Outputs : a : 5 , b : 10
14 }
15 }

1.3 Value Types, Reference Types, Immutable, Semantics


What is the difference between value types and reference types in C#?
Value types store the actual data and are stored on the stack, while reference types store a reference
to the data (a memory address) and are stored on the heap. Value types are copied when assigned
to another variable, while reference types share the same memory reference.

Listing 1.39: Code example

1 struct ValueTypeExample
2 {
3 public int x ;
4 }
5

6 class ReferenceTypeExample
7 {
8 public int x ;
9 }
10

11 class Program
12 {
13 static void Main ()
14 {
15 // Value type
16 ValueTypeExample v1 = new ValueTypeExample { x = 10 };
17 ValueTypeExample v2 = v1 ; // v2 is a copy of v1
18 v2 . x = 20; // Changes only v2 , v1 remains unchanged

22
1.3. Value Types, Reference Types, Immutable, Semantics

19

20 // Reference type
21 ReferenceTypeExample r1 = new ReferenceTypeExample { x = 10 };
22 ReferenceTypeExample r2 = r1 ; // r2 references the same object as r1
23 r2 . x = 20; // Changes affect both r1 and r2
24

25 Console . WriteLine ( $ " Value Type : v1 . x = { v1 . x } , v2 . x = { v2 . x } " ) ;


26 Console . WriteLine ( $ " Reference Type : r1 . x = { r1 . x } , r2 . x = { r2 . x } " ) ;
27 }
28 }

How does boxing and unboxing work in C#?


Boxing is the process of converting a value type to an object, which stores the value on the heap.
Unboxing is the reverse, where an object is cast back to a value type. Boxing incurs a performance
cost because it involves heap allocation.

Listing 1.40: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 int value = 123;
6 object boxed = value ; // Boxing : value type to object
7 int unboxed = ( int ) boxed ; // Unboxing : object back to value type
8

9 Console . WriteLine ( $ " Boxed : { boxed } , Unboxed : { unboxed } " ) ;


10 }
11 }

What is the difference between mutable and immutable types in C#?


Mutable types can be modified after creation, whereas immutable types cannot be changed once
created. Strings are an example of an immutable type, while most custom objects are mutable
unless explicitly designed to be immutable.

Listing 1.41: Code example

1 class MutableClass
2 {
3 public int Value { get ; set ; }
4 }
5

6 class ImmutableClass
7 {

23
1.3. Value Types, Reference Types, Immutable, Semantics

8 public int Value { get ; }


9

10 public ImmutableClass ( int value )


11 {
12 Value = value ;
13 }
14 }
15

16 class Program
17 {
18 static void Main ()
19 {
20 var mutable = new MutableClass { Value = 10 };
21 mutable . Value = 20; // Can change after creation
22

23 var immutable = new ImmutableClass (10) ;


24 // immutable . Value = 20; // Error : Cannot assign to ’ Value ’ because it is
read - only
25 }
26 }

What are the performance implications of value types vs. reference


types?
Value types are stored on the stack and can lead to better performance for small data because they
avoid heap allocations. However, copying large value types can be expensive. Reference types are
stored on the heap and managed by the garbage collector, which can introduce overhead.

Listing 1.42: Code example

1 struct PointValueType
2 {
3 public int X , Y ;
4 }
5

6 class PointReferenceType
7 {
8 public int X , Y ;
9 }
10

11 class Program
12 {
13 static void Main ()
14 {
15 // Value type : Faster for small structs but copies all data
16 PointValueType p1 = new PointValueType { X = 10 , Y = 20 };

24
1.3. Value Types, Reference Types, Immutable, Semantics

17 PointValueType p2 = p1 ; // Full copy of the data


18 p2 . X = 30;
19

20 // Reference type : More efficient for large objects , but managed by GC


21 PointReferenceType r1 = new PointReferenceType { X = 10 , Y = 20 };
22 PointReferenceType r2 = r1 ; // Reference to the same object
23 r2 . X = 30;
24 }
25 }

How do structs and classes differ in terms of copying semantics in C#?


Structs are value types, meaning they are copied by value. When you assign one struct to another,
the entire content is copied. Classes are reference types, so when one class instance is assigned to
another, only the reference is copied.

Listing 1.43: Code example

1 struct StructExample
2 {
3 public int X ;
4 }
5

6 class ClassExample
7 {
8 public int X ;
9 }
10

11 class Program
12 {
13 static void Main ()
14 {
15 StructExample s1 = new StructExample { X = 10 };
16 StructExample s2 = s1 ; // Copies the entire struct
17 s2 . X = 20; // s1 . X remains 10
18

19 ClassExample c1 = new ClassExample { X = 10 };


20 ClassExample c2 = c1 ; // Copies the reference
21 c2 . X = 20; // c1 . X is also 20 , since both c1 and c2 reference the same
object
22 }
23 }

25
1.3. Value Types, Reference Types, Immutable, Semantics

What are the common pitfalls of using mutable reference types in C#?
The common pitfalls include unintended side effects where modifying one reference type instance
affects others that share the same reference, leading to difficult-to-track bugs. This occurs because
reference types are passed by reference by default.

Listing 1.44: Code example

1 class MutableExample
2 {
3 public int Value { get ; set ; }
4 }
5

6 class Program
7 {
8 static void Main ()
9 {
10 MutableExample obj1 = new MutableExample { Value = 10 };
11 MutableExample obj2 = obj1 ; // obj2 references the same object as obj1
12 obj2 . Value = 20; // Changing obj2 . Value also changes obj1 . Value
13

14 Console . WriteLine ( $ " obj1 . Value : { obj1 . Value } " ) ; // Outputs 20


15 }
16 }

How does the ‘in‘ parameter modifier affect value types in C#?
The ‘in‘ keyword is used to pass value types by reference but prevents modification. It is especially
useful for passing large structs without copying them while ensuring the callee cannot modify the
value.

Listing 1.45: Code example

1 struct LargeStruct
2 {
3 public int X ;
4 public int Y ;
5 }
6

7 class Program
8 {
9 static void DisplayCoordinates ( in LargeStruct point )
10 {
11 // point . X = 10; // Error : Cannot modify because of ’ in ’ keyword
12 Console . WriteLine ( $ " X : { point . X } , Y : { point . Y } " ) ;
13 }
14

26
1.3. Value Types, Reference Types, Immutable, Semantics

15 static void Main ()


16 {
17 LargeStruct point = new LargeStruct { X = 5 , Y = 10 };
18 DisplayCoordinates ( in point ) ; // Passed by reference , but not modifiable
19 }
20 }

What are the key differences between shallow copy and deep copy in C#?
A shallow copy duplicates the top-level object, but any references within that object still point to
the original objects. A deep copy duplicates the object and all the objects it references, creating
an entirely independent copy.

Listing 1.46: Code example

1 class Person
2 {
3 public string Name { get ; set ; }
4 public Address Address { get ; set ; }
5 }
6

7 class Address
8 {
9 public string City { get ; set ; }
10 }
11

12 class Program
13 {
14 static void Main ()
15 {
16 // Shallow copy
17 Person person1 = new Person { Name = " John " , Address = new Address { City
= " NY " } };
18 Person person2 = person1 ; // Shallow copy
19 person2 . Address . City = " LA " ; // Changes the Address of person1 as well
20

21 Console . WriteLine ( $ " Person1 City : { person1 . Address . City } " ) ; // Outputs LA
22 }
23 }

How does the ‘ref‘ keyword affect the behavior of reference and value
types when passed as parameters?
The ‘ref‘ keyword allows both value and reference types to be passed by reference, enabling mod-
ifications within the method to affect the original variable.

27
1.3. Value Types, Reference Types, Immutable, Semantics

Listing 1.47: Code example

1 class Program
2 {
3 static void ModifyValue ( ref int value )
4 {
5 value = 20; // Modifies the original value
6 }
7

8 static void Main ()


9 {
10 int number = 10;
11 ModifyValue ( ref number ) ; // Passing by reference
12 Console . WriteLine ( number ) ; // Outputs 20
13 }
14 }

What are tuples in C#, and are they value types or reference types?
Tuples are a data structure used to store a sequence of values. In C# 7.0 and later, tuples are
value types and provide a lightweight way to return multiple values from a method. They are
mutable but considered value types.

Listing 1.48: Code example

1 class Program
2 {
3 static ( int , string ) GetData ()
4 {
5 return (1 , " example " ) ; // Returns a tuple
6 }
7

8 static void Main ()


9 {
10 var tuple = GetData () ;
11 Console . WriteLine ( $ " ID : { tuple . Item1 } , Name : { tuple . Item2 } " ) ; // Outputs :
ID : 1 , Name : example
12 }
13 }

What is semantic meaning in C#, and how does it differ from syntax?
Semantics refers to the meaning or behavior of the code when executed, while syntax refers to
the rules governing how the code is written. Correct syntax doesn’t always guarantee correct
semantics. For example, the code may compile but still produce incorrect results due to logical
errors.

28
1.3. Value Types, Reference Types, Immutable, Semantics

Listing 1.49: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 int x = 5;
6 int y = 0;
7

8 // Correct syntax , but incorrect semantics ( division by zero )


9 int result = x / y ; // This will throw a runtime exception
10 }
11 }

How can you implement immutability in C# for custom objects?


To implement immutability, you need to make all fields ‘readonly‘ and avoid providing ‘set‘ acces-
sors for properties. All fields should be initialized in the constructor.

Listing 1.50: Code example

1 class ImmutablePerson
2 {
3 public string Name { get ; }
4 public int Age { get ; }
5

6 public ImmutablePerson ( string name , int age )


7 {
8 Name = name ;
9 Age = age ;
10 }
11 }
12

13 class Program
14 {
15 static void Main ()
16 {
17 var person = new ImmutablePerson ( " John " , 30) ;
18 // person . Age = 31; // Error : Cannot modify because the property is
readonly
19 }
20 }

29
1.3. Value Types, Reference Types, Immutable, Semantics

How does the garbage collector handle value types and reference types
differently?
Value types are typically stored on the stack and are automatically reclaimed when they go out
of scope. Reference types are stored on the heap, and the garbage collector is responsible for
reclaiming memory when they are no longer referenced.

Listing 1.51: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 // Value type ( stored on stack )
6 int value = 10;
7

8 // Reference type ( stored on heap )


9 object reference = new object () ;
10

11 // The garbage collector will automatically clean up reference when no


longer in use .
12 }
13 }

Why should you avoid using mutable value types in C#?


Mutable value types can lead to unexpected behavior because they are copied by value. Changes
made to a copy do not affect the original, which can be confusing in certain contexts like collection
manipulation.

Listing 1.52: Code example

1 struct MutableStruct
2 {
3 public int Value ;
4 }
5

6 class Program
7 {
8 static void ModifyStruct ( MutableStruct s )
9 {
10 s . Value = 20; // This change affects only the local copy
11 }
12

13 static void Main ()


14 {
15 MutableStruct s = new MutableStruct { Value = 10 };

30
1.3. Value Types, Reference Types, Immutable, Semantics

16 ModifyStruct ( s ) ;
17 Console . WriteLine ( s . Value ) ; // Outputs : 10 , since the original was not
modified
18 }
19 }

What is ‘readonly struct‘ in C#, and when would you use it?
A ‘readonly struct‘ ensures that all fields within the struct are immutable after initialization.
It improves performance by avoiding unnecessary defensive copies when passing the struct by
reference.

Listing 1.53: Code example

1 readonly struct ReadonlyPoint


2 {
3 public int X { get ; }
4 public int Y { get ; }
5

6 public ReadonlyPoint ( int x , int y )


7 {
8 X = x;
9 Y = y;
10 }
11 }
12

13 class Program
14 {
15 static void Main ()
16 {
17 ReadonlyPoint point = new ReadonlyPoint (10 , 20) ;
18 // point . X = 30; // Error : Cannot modify readonly field
19 }
20 }

What are default values for value types and reference types in C#?
Value types have default values based on their type (e.g., ‘0‘ for ‘int‘, ‘false‘ for ‘bool‘), whereas
reference types default to ‘null‘.

Listing 1.54: Code example

1 class Program
2 {
3 static void Main ()
4 {

31
1.4. Types and Type Differences

5 int defaultValue = default ( int ) ; // Outputs 0


6 object defaultReference = default ( object ) ; // Outputs null
7

8 Console . WriteLine ( $ " Default value type : { defaultValue } " ) ;


9 Console . WriteLine ( $ " Default reference type : { defaultReference } " ) ;
10 }
11 }

1.4 Types and Type Differences


What is the difference between ‘int‘ and ‘Int32‘ in C#?
‘int‘ is an alias for ‘System.Int32‘ in C#. Both represent a 32-bit signed integer and are functionally
identical. The alias exists for convenience and readability in the C# language.

Listing 1.55: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 int a = 10;
6 Int32 b = 20; // Both are the same , ‘int ‘ is an alias for ‘ Int32 ‘
7 Console . WriteLine ( a + b ) ; // Outputs 30
8 }
9 }

What is the difference between ‘float‘, ‘double‘, and ‘decimal‘ in C#?


When would you use each?
• float: 32-bit single-precision floating-point type, used for fractional numbers where precision
is less critical.
• double: 64-bit double-precision floating-point type, for most general-purpose calculations.
• decimal: 128-bit high-precision floating-point type, ideal for financial calculations requiring
high accuracy.

Listing 1.56: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 float f = 3.14 f ; // Single - precision
6 double d = 3.14159; // Double - precision

32
1.4. Types and Type Differences

7 decimal m = 3.14159 m ; // High - precision , used for monetary calculations


8

9 Console . WriteLine ( $ " Float : { f } , Double : { d } , Decimal : { m } " ) ;


10 }
11 }

What is the difference between ‘struct‘ and ‘class‘ in C#?


• struct: A value type stored on the stack, copied by value. Good for small data structures
without inheritance.
• class: A reference type stored on the heap, passed by reference. Good for complex data
structures requiring inheritance.

Listing 1.57: Code example

1 struct PointStruct
2 {
3 public int X , Y ;
4 }
5

6 class PointClass
7 {
8 public int X , Y ;
9 }
10

11 class Program
12 {
13 static void Main ()
14 {
15 PointStruct pStruct = new PointStruct { X = 10 , Y = 20 };
16 PointClass pClass = new PointClass { X = 10 , Y = 20 };
17

18 PointStruct pStructCopy = pStruct ; // Value type copy


19 PointClass pClassCopy = pClass ; // Reference type copy ( both point to the
same object )
20

21 pStructCopy . X = 100;
22 pClassCopy . X = 100;
23

24 Console . WriteLine ( $ " Struct original : { pStruct . X } , copy : { pStructCopy . X } " ) ;


// Outputs : 10 , 100
25 Console . WriteLine ( $ " Class original : { pClass . X } , copy : { pClassCopy . X } " ) ; //
Outputs : 100 , 100
26 }
27 }

33
1.4. Types and Type Differences

What is the difference between a reference type and a value type in C#?
• Value types: Store data directly on the stack, copied by value.
• Reference types: Store references on the heap, share the same memory address when
assigned.

Listing 1.58: Code example

1 struct ValueType
2 {
3 public int Data ;
4 }
5

6 class ReferenceType
7 {
8 public int Data ;
9 }
10

11 class Program
12 {
13 static void Main ()
14 {
15 ValueType value1 = new ValueType { Data = 10 };
16 ValueType value2 = value1 ; // Copy of the value
17 value2 . Data = 20;
18

19 ReferenceType ref1 = new ReferenceType { Data = 10 };


20 ReferenceType ref2 = ref1 ; // Reference to the same object
21 ref2 . Data = 20;
22

23 Console . WriteLine ( $ " Value Type : { value1 . Data } " ) ; // Outputs : 10


24 Console . WriteLine ( $ " Reference Type : { ref1 . Data } " ) ; // Outputs : 20
25 }
26 }

What is the difference between ‘dynamic‘ and ‘var‘ in C#?


• var: Type determined at compile time, once inferred, it cannot change.
• dynamic: Type resolved at runtime, offering flexibility but losing compile-time checks.

Listing 1.59: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 var a = 10; // Type inferred as int at compile time

34
1.4. Types and Type Differences

6 // a = " string "; // Compile - time error


7

8 dynamic b = 10; // Type determined at runtime


9 b = " string " ; // No error , but potential runtime issues
10 Console . WriteLine ( b ) ;
11 }
12 }

What is the difference between ‘object‘ and ‘dynamic‘ in C#?


• object: Base type for all types in C#, requires casting for operations.
• dynamic: Bypasses compile-time type checking; operations are resolved at runtime.

Listing 1.60: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 object obj = 10;
6 // Console . WriteLine ( obj + 5) ; // Compile - time error , requires casting
7

8 dynamic dyn = 10;


9 Console . WriteLine ( dyn + 5) ; // No error , resolved at runtime
10 }
11 }

What is the difference between ‘nullable‘ types and ‘non-nullable‘ types


in C#?
• Non-nullable: Cannot hold null and must have a valid value.
• Nullable (T?): Can represent all values of the underlying type plus null.

Listing 1.61: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 int nonNullable = 10;
6 // nonNullable = null ; // Compile - time error , cannot assign null to non -
nullable
7

8 int ? nullable = null ; // Nullable type can hold a null value


9 nullable = 20; // Can also hold a valid integer value
10 Console . WriteLine ( nullable . HasValue ? nullable . Value . ToString () : " null " ) ;

35
1.4. Types and Type Differences

11 }
12 }

How does type casting work between different types in C#? What are
implicit and explicit casts?
• Implicit cast: Happens automatically when no data loss occurs.
• Explicit cast: Must be specified with the cast operator, can cause data loss or fail at
runtime.

Listing 1.62: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 // Implicit cast : int to double ( no data loss )
6 int a = 10;
7 double b = a ;
8 Console . WriteLine ( b ) ; // Outputs : 10.0
9

10 // Explicit cast : double to int ( possible data loss )


11 double c = 9.8;
12 int d = ( int ) c ; // Must explicitly cast
13 Console . WriteLine ( d ) ; // Outputs : 9
14 }
15 }

What is the difference between ‘is‘ and ‘as‘ operators in C#?


• is: Checks if an object is compatible with a given type and returns a boolean.
• as: Attempts to cast an object to a type, returning null if the cast fails instead of throwing.

Listing 1.63: Code example

1 class Animal { }
2 class Dog : Animal { }
3

4 class Program
5 {
6 static void Main ()
7 {
8 Animal animal = new Dog () ;
9

10 if ( animal is Dog )
11 {

36
1.4. Types and Type Differences

12 Console . WriteLine ( " Animal is a Dog . " ) ;


13 }
14

15 Dog dog = animal as Dog ; // Safe cast , returns null if cast fails
16 if ( dog != null )
17 {
18 Console . WriteLine ( " Successfully cast to Dog . " ) ;
19 }
20 }
21 }

How does the ‘typeof‘ keyword differ from ‘GetType()‘ in C#?


• typeof: Retrieves a type at compile time using a type name.
• GetType(): Retrieves the runtime type of an object instance.
Listing 1.64: Code example

1 class Animal { }
2

3 class Program
4 {
5 static void Main ()
6 {
7 // Compile - time type
8 Type type1 = typeof ( Animal ) ;
9 Console . WriteLine ( type1 ) ; // Outputs : Animal
10

11 // Runtime type
12 Animal animal = new Animal () ;
13 Type type2 = animal . GetType () ;
14 Console . WriteLine ( type2 ) ; // Outputs : Animal
15 }
16 }

What is the difference between ‘enum‘ and ‘constant‘ in C#?


• enum: A distinct type representing a set of named constants.
• const: A single constant value, must be known at compile time and cannot change.
Listing 1.65: Code example

1 enum Days { Sunday , Monday , Tuesday }


2

3 class Program
4 {
5 const int MaxValue = 100; // A constant

37
1.4. Types and Type Differences

7 static void Main ()


8 {
9 Days day = Days . Monday ; // Using an enum
10 Console . WriteLine ( $ " Day : { day } , MaxValue : { MaxValue } " ) ;
11 }
12 }

What is a ‘delegate‘ in C#, and how does it differ from a ‘Func‘ or


‘Action‘ type?
A ‘delegate‘ is a type representing references to methods with a particular signature. ‘Func‘ and
‘Action‘ are predefined delegates where ‘Func‘ returns a value and ‘Action‘ does not.

Listing 1.66: Code example

1 // Custom delegate
2 public delegate int MathOperation ( int x , int y ) ;
3

4 class Program
5 {
6 static int Add ( int x , int y ) = > x + y ;
7

8 static void Main ()


9 {
10 MathOperation operation = Add ;
11 Console . WriteLine ( operation (5 , 10) ) ; // Outputs 15
12

13 Func < int , int , int > funcOperation = Add ;


14 Console . WriteLine ( funcOperation (5 , 10) ) ; // Outputs 15
15 }
16 }

What are type parameters in C#, and how do they work with generics?
Type parameters are placeholders in generic classes or methods, allowing for type-safe code that
works with any data type without duplication.

Listing 1.67: Code example

1 // Generic class
2 class GenericBox <T >
3 {
4 public T Value { get ; set ; }
5 }
6

38
1.4. Types and Type Differences

7 class Program
8 {
9 static void Main ()
10 {
11 GenericBox < int > intBox = new GenericBox < int > { Value = 10 };
12 GenericBox < string > strBox = new GenericBox < string > { Value = " Hello " };
13

14 Console . WriteLine ( $ " Int Box : { intBox . Value } , String Box : { strBox . Value } " ) ;
15 }
16 }

How does method overloading work in C#, and how does it differ from
method overriding?
• Overloading: Same method name, different parameter signatures within the same class.
• Overriding: A derived class changes the behavior of a base class method using override.

Listing 1.68: Code example

1 class BaseClass
2 {
3 public virtual void Display () = > Console . WriteLine ( " BaseClass Display " ) ;
4 }
5

6 class DerivedClass : BaseClass


7 {
8 public override void Display () = > Console . WriteLine ( " DerivedClass Display " ) ;
9

10 // Overloaded method
11 public void Display ( string message ) = > Console . WriteLine ( message ) ;
12 }
13

14 class Program
15 {
16 static void Main ()
17 {
18 DerivedClass derived = new DerivedClass () ;
19 derived . Display () ; // Calls overridden method
20 derived . Display ( " Hello " ) ; // Calls overloaded method
21 }
22 }

39
1.4. Types and Type Differences

How does the ‘default‘ keyword work in C#, and what is its purpose
with generic types?
‘default‘ returns the default value of a type. For value types, it’s typically ‘0‘ or ‘false‘, and for
reference types, ‘null‘. In generics, ‘default‘ ensures the code works for any type parameter.

Listing 1.69: Code example

1 class Program
2 {
3 static T GetDefaultValue <T >()
4 {
5 return default ( T ) ; // Returns default value based on the type
6 }
7

8 static void Main ()


9 {
10 Console . WriteLine ( GetDefaultValue < int >() ) ; // Outputs 0
11 Console . WriteLine ( GetDefaultValue < string >() ) ; // Outputs null
12 }
13 }

What is the difference between implicit and explicit type conversion in


C#?
• Implicit: Occurs automatically when safe.
• Explicit: Requires a cast operator because data loss or failure can happen.

Listing 1.70: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 int num = 100;
6 double numDouble = num ; // Implicit conversion
7

8 double value = 9.8;


9 int valueInt = ( int ) value ; // Explicit conversion
10

11 Console . WriteLine ( $ " Implicit : { numDouble } , Explicit : { valueInt } " ) ;


12 }
13 }

40
1.4. Types and Type Differences

How does the ‘nullable‘ feature work in C# with value types?


Nullable value types (‘T¿) can represent all values of the underlying type plus ‘null‘. Useful when
a value may or may not exist.

Listing 1.71: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 int ? nullableInt = null ; // Nullable integer
6 if ( nullableInt . HasValue )
7 {
8 Console . WriteLine ( nullableInt . Value ) ;
9 }
10 else
11 {
12 Console . WriteLine ( " Value is null " ) ;
13 }
14 }
15 }

How does ‘ref‘ and ‘out‘ differ when passing parameters in C#?
• ref: Passes a parameter by reference; must be initialized before passing.
• out: Also by reference, but the parameter needn’t be initialized beforehand. Must be
assigned inside the method.

Listing 1.72: Code example

1 class Program
2 {
3 static void ModifyRef ( ref int a ) = > a = 20;
4 static void ModifyOut ( out int a ) = > a = 30;
5

6 static void Main ()


7 {
8 int refValue = 10;
9 ModifyRef ( ref refValue ) ;
10 Console . WriteLine ( refValue ) ; // Outputs : 20
11

12 int outValue ;
13 ModifyOut ( out outValue ) ;
14 Console . WriteLine ( outValue ) ; // Outputs : 30
15 }
16 }

41
1.5. Collections and LINQ

1.5 Collections and LINQ


What is the difference between ‘IEnumerable‘, ‘ICollection‘, and ‘IList‘
in C#?
• IEnumerable: Forward-only iteration, minimal methods.
• ICollection: Inherits IEnumerable, adds Add/Remove/Count.
• IList: Inherits ICollection, provides index-based access.

Listing 1.73: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 // IEnumerable : Only allows iteration
6 IEnumerable < int > enumerable = new List < int > { 1 , 2 , 3 };
7 foreach ( var item in enumerable )
8 {
9 Console . WriteLine ( item ) ;
10 }
11

12 // ICollection : Allows adding / removing


13 ICollection < int > collection = new List < int > { 1 , 2 , 3 };
14 collection . Add (4) ;
15 collection . Remove (2) ;
16

17 // IList : Allows index - based access


18 IList < int > list = new List < int > { 1 , 2 , 3 };
19 list [1] = 10;
20 list . Insert (2 , 5) ;
21 Console . WriteLine ( string . Join ( " , " , list ) ) ; // Outputs : 1 , 10 , 5 , 3
22 }
23 }

How does deferred execution work in LINQ, and why is it useful?


LINQ queries aren’t executed until the data is actually requested. This defers expensive operations
and allows combining multiple queries before execution.

Listing 1.74: Code example

1 class Program
2 {
3 static void Main ()
4 {

42
1.5. Collections and LINQ

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

7 // Deferred execution : query is not executed until we enumerate over it


8 var query = numbers . Where ( n = > n > 2) ;
9

10 numbers . Add (6) ; // Adding to the list before enumeration


11

12 // Now the query is executed


13 foreach ( var number in query )
14 {
15 Console . WriteLine ( number ) ; // Outputs : 3 , 4 , 5 , 6
16 }
17 }
18 }

What is the difference between ‘First‘, ‘FirstOrDefault‘, ‘Single‘, and


‘SingleOrDefault‘ in LINQ? When should each be used?
• First: Returns the first element, throws if empty.
• FirstOrDefault: Returns the first element or a default if empty.
• Single: Returns exactly one element, throws if none or more than one.
• SingleOrDefault: Returns exactly one element or default if empty, throws if more than
one.

Listing 1.75: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 List < int > numbers = new List < int > { 1 , 2 , 3 };
6

7 // First : throws if empty , returns first element


8 int first = numbers . First () ;
9

10 // FirstOrDefault : returns default if empty


11 int firstOrDefault = numbers . FirstOrDefault () ;
12

13 // Single : expects only one element , throws if more than one


14 int single = numbers . Where ( n = > n == 2) . Single () ;
15

16 // SingleOrDefault : returns single element or default , throws if more than


one
17 int singleOrDefault = numbers . Where ( n = > n == 2) . SingleOrDefault () ;
18 }
19 }

43
1.5. Collections and LINQ

How does the ‘SelectMany‘ method differ from ‘Select‘ in LINQ?


‘Select‘ projects each element into a new form, while ‘SelectMany‘ flattens sequences of sequences
into a single sequence.

Listing 1.76: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 // Using Select : Returns an IEnumerable of IEnumerable
6 List < List < int > > numbers = new List < List < int > >
7 {
8 new List < int > { 1 , 2 } ,
9 new List < int > { 3 , 4 }
10 };
11 var selectResult = numbers . Select ( list = > list ) ;
12

13 // Using SelectMany : Flattens the lists into a single sequence


14 var selectManyResult = numbers . SelectMany ( list = > list ) ;
15

16 Console . WriteLine ( string . Join ( " , " , selectManyResult ) ) ; // Outputs : 1 , 2 ,


3, 4
17 }
18 }

How does LINQ handle filtering with ‘Where‘, and how does it work with
complex objects?
‘Where‘ filters a sequence based on a predicate. It applies to any object type by specifying the
condition in a lambda expression.

Listing 1.77: Code example

1 class Person
2 {
3 public string Name { get ; set ; }
4 public int Age { get ; set ; }
5 }
6

7 class Program
8 {
9 static void Main ()
10 {
11 List < Person > people = new List < Person >
12 {

44
1.5. Collections and LINQ

13 new Person { Name = " Alice " , Age = 30 } ,


14 new Person { Name = " Bob " , Age = 40 } ,
15 new Person { Name = " Charlie " , Age = 25 }
16 };
17

18 // Filtering using Where on complex objects


19 var adults = people . Where ( p = > p . Age >= 30) ;
20

21 foreach ( var person in adults )


22 {
23 Console . WriteLine ( person . Name ) ; // Outputs : Alice , Bob
24 }
25 }
26 }

What is the purpose of ‘GroupBy‘ in LINQ, and how does it work?


‘GroupBy‘ groups elements by a key selector and returns an ‘IEnumerable<IGrouping<TKey,
TElement»‘, where each grouping has a key and its related items.

Listing 1.78: Code example

1 class Person
2 {
3 public string Name { get ; set ; }
4 public string Department { get ; set ; }
5 }
6

7 class Program
8 {
9 static void Main ()
10 {
11 List < Person > people = new List < Person >
12 {
13 new Person { Name = " Alice " , Department = " HR " } ,
14 new Person { Name = " Bob " , Department = " IT " } ,
15 new Person { Name = " Charlie " , Department = " IT " }
16 };
17

18 // Group by department
19 var groupedByDepartment = people . GroupBy ( p = > p . Department ) ;
20

21 foreach ( var group in groupedByDepartment )


22 {
23 Console . WriteLine ( $ " Department : { group . Key } " ) ;
24 foreach ( var person in group )
25 {

45
1.5. Collections and LINQ

26 Console . WriteLine ( $ " - { person . Name } " ) ;


27 }
28 }
29 }
30 }

How does ‘ToDictionary‘ work in LINQ, and what are some potential
pitfalls?
‘ToDictionary‘ converts a sequence into a ‘Dictionary<TKey,TValue>‘ using key/value selectors.
A pitfall is duplicate keys, which cause an exception.

Listing 1.79: Code example

1 class Person
2 {
3 public string Name { get ; set ; }
4 public int Id { get ; set ; }
5 }
6

7 class Program
8 {
9 static void Main ()
10 {
11 List < Person > people = new List < Person >
12 {
13 new Person { Name = " Alice " , Id = 1 } ,
14 new Person { Name = " Bob " , Id = 2 }
15 };
16

17 // Converting to a Dictionary where key is Id and value is Name


18 Dictionary < int , string > peopleDict = people . ToDictionary ( p = > p . Id , p = > p
. Name ) ;
19

20 // Accessing the dictionary


21 Console . WriteLine ( peopleDict [1]) ; // Outputs : Alice
22 }
23 }

How do LINQ ‘Join‘ and ‘GroupJoin‘ differ, and when should each be
used?
• Join: Combines two sequences based on matching keys, similar to an inner join.
• GroupJoin: Groups the matching elements from the second sequence, akin to a left join.

46
1.5. Collections and LINQ

Listing 1.80: Code example

1 class Employee
2 {
3 public int Id { get ; set ; }
4 public string Name { get ; set ; }
5 }
6

7 class Department
8 {
9 public int DepartmentId { get ; set ; }
10 public string DepartmentName { get ; set ; }
11 }
12

13 class Program
14 {
15 static void Main ()
16 {
17 List < Employee > employees = new List < Employee >
18 {
19 new Employee { Id = 1 , Name = " Alice " } ,
20 new Employee { Id = 2 , Name = " Bob " }
21 };
22

23 List < Department > departments = new List < Department >
24 {
25 new Department { DepartmentId = 1 , DepartmentName = " HR " } ,
26 new Department { DepartmentId = 2 , DepartmentName = " IT " }
27 };
28

29 // Join example
30 var joinResult = employees . Join ( departments ,
31 e = > e . Id ,
32 d = > d . DepartmentId ,
33 (e , d ) = > new { e . Name , d . DepartmentName
}) ;
34

35 // GroupJoin example ( left outer join )


36 var groupJoinResult = departments . GroupJoin ( employees ,
37 d = > d . DepartmentId ,
38 e = > e . Id ,
39 (d , es ) = > new { d .
DepartmentName , Employees =
es }) ;
40 }
41 }

47
1.5. Collections and LINQ

What is the difference between ‘OrderBy‘ and ‘ThenBy‘ in LINQ?


• OrderBy: Sorts items in ascending order by a key.
• ThenBy: Specifies a secondary sort for items that matched the primary sort.

Listing 1.81: Code example

1 class Person
2 {
3 public string Name { get ; set ; }
4 public int Age { get ; set ; }
5 }
6

7 class Program
8 {
9 static void Main ()
10 {
11 List < Person > people = new List < Person >
12 {
13 new Person { Name = " Alice " , Age = 30 } ,
14 new Person { Name = " Bob " , Age = 25 } ,
15 new Person { Name = " Charlie " , Age = 30 }
16 };
17

18 // Sort by Age , then by Name for people with the same Age
19 var sortedPeople = people . OrderBy ( p = > p . Age ) . ThenBy ( p = > p . Name ) ;
20

21 foreach ( var person in sortedPeople )


22 {
23 Console . WriteLine ( $ " { person . Name } , { person . Age } " ) ;
24 }
25 }
26 }

What is the purpose of ‘Distinct‘ in LINQ, and how does it determine


uniqueness?
‘Distinct‘ removes duplicate elements from a sequence by using the default or custom equality
comparer.

Listing 1.82: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 List < int > numbers = new List < int > { 1 , 2 , 2 , 3 , 4 , 4 , 5 };

48
1.5. Collections and LINQ

7 // Using Distinct to eliminate duplicates


8 var distinctNumbers = numbers . Distinct () ;
9

10 Console . WriteLine ( string . Join ( " , " , distinctNumbers ) ) ; // Outputs : 1 , 2 ,


3, 4, 5
11 }
12 }

How does ‘Zip‘ work in LINQ, and when would you use it?
‘Zip‘ pairs up elements from two sequences by index, stopping when the shorter sequence ends,
and applies a function to each pair.
Listing 1.83: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 var numbers = new List < int > { 1 , 2 , 3 };
6 var letters = new List < char > { ’A ’ , ’B ’ , ’C ’ };
7

8 // Zipping numbers with letters


9 var zipped = numbers . Zip ( letters , (n , l ) = > $ " { n } -{ l } " ) ;
10

11 foreach ( var item in zipped )


12 {
13 Console . WriteLine ( item ) ; // Outputs : 1 -A , 2 -B , 3 - C
14 }
15 }
16 }

How can ‘Any‘ and ‘All‘ be used for validation in LINQ?


• Any: Checks if at least one element satisfies a condition or if the sequence has elements.
• All: Checks if all elements satisfy a condition.
Listing 1.84: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 List < int > numbers = new List < int > { 1 , 2 , 3 , 4 , 5 };
6

7 // Check if any number is greater than 4

49
1.5. Collections and LINQ

8 bool anyGreaterThanFour = numbers . Any ( n = > n > 4) ; // True


9

10 // Check if all numbers are positive


11 bool allPositive = numbers . All ( n = > n > 0) ; // True
12

13 Console . WriteLine ( anyGreaterThanFour ) ; // Outputs : True


14 Console . WriteLine ( allPositive ) ; // Outputs : True
15 }
16 }

What is ‘Aggregate‘ in LINQ, and how does it differ from ‘Sum‘, ‘Count‘,
etc.?
‘Aggregate‘ applies a custom accumulator function over a sequence, whereas methods like ‘Sum‘
or ‘Count‘ are specific built-in aggregations.

Listing 1.85: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 List < int > numbers = new List < int > { 1 , 2 , 3 , 4 };
6

7 // Using Aggregate to compute product of all numbers


8 int product = numbers . Aggregate (( acc , n ) = > acc * n ) ;
9

10 Console . WriteLine ( product ) ; // Outputs : 24 (1 * 2 * 3 * 4)


11 }
12 }

How does ‘Except‘ differ from ‘Intersect‘ in LINQ?


• Except: Returns elements in the first sequence not in the second.
• Intersect: Returns elements present in both sequences.

Listing 1.86: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 List < int > list1 = new List < int > { 1 , 2 , 3 , 4 };
6 List < int > list2 = new List < int > { 3 , 4 , 5 , 6 };
7

8 // Elements in list1 but not in list2

50
1.5. Collections and LINQ

9 var exceptResult = list1 . Except ( list2 ) ;


10 Console . WriteLine ( string . Join ( " , " , exceptResult ) ) ; // Outputs : 1 , 2
11

12 // Elements common to both lists


13 var intersectResult = list1 . Intersect ( list2 ) ;
14 Console . WriteLine ( string . Join ( " , " , intersectResult ) ) ; // Outputs : 3 , 4
15 }
16 }

How does ‘Select‘ with index work in LINQ, and why would you use it?
‘Select‘ can include the zero-based index in the projection. Useful for transformations that need
both the item and its index.

Listing 1.87: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 var fruits = new List < string > { " Apple " , " Banana " , " Cherry " };
6

7 // Using Select with index


8 var indexedFruits = fruits . Select (( fruit , index ) = > $ " { index }: { fruit } " ) ;
9

10 foreach ( var item in indexedFruits )


11 {
12 Console . WriteLine ( item ) ; // Outputs : 0: Apple , 1: Banana , 2: Cherry
13 }
14 }
15 }

How can you use ‘Concat‘ and ‘Union‘ in LINQ, and what is the differ-
ence?
• Concat: Merges two sequences end to end, keeping duplicates.
• Union: Merges two sequences and removes duplicates.

Listing 1.88: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 var list1 = new List < int > { 1 , 2 , 3 };
6 var list2 = new List < int > { 3 , 4 , 5 };

51
1.5. Collections and LINQ

8 // Concatenating two lists


9 var concatResult = list1 . Concat ( list2 ) ;
10 Console . WriteLine ( string . Join ( " , " , concatResult ) ) ; // Outputs : 1 , 2 , 3 ,
3, 4, 5
11

12 // Union of two lists ( removes duplicates )


13 var unionResult = list1 . Union ( list2 ) ;
14 Console . WriteLine ( string . Join ( " , " , unionResult ) ) ; // Outputs : 1 , 2 , 3 , 4 ,
5
15 }
16 }

What is the difference between ‘Take‘ and ‘Skip‘ in LINQ, and when
would you use them?
• Take: Returns a specified number of elements from the start of a sequence.
• Skip: Ignores a specified number of elements, returning the rest.

Listing 1.89: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 var numbers = new List < int > { 1 , 2 , 3 , 4 , 5 , 6 };
6

7 // Take the first 3 elements


8 var taken = numbers . Take (3) ;
9 Console . WriteLine ( string . Join ( " , " , taken ) ) ; // Outputs : 1 , 2 , 3
10

11 // Skip the first 3 elements and return the rest


12 var skipped = numbers . Skip (3) ;
13 Console . WriteLine ( string . Join ( " , " , skipped ) ) ; // Outputs : 4 , 5 , 6
14 }
15 }

How does the ‘ToLookup‘ method differ from ‘GroupBy‘ in LINQ?


‘ToLookup‘ returns an immutable ‘Lookup<TKey,TElement>‘ that you can query quickly, while
‘GroupBy‘ returns an ‘IEnumerable<IGrouping<TKey,TElement»‘. Semantically similar, but
‘Lookup‘ is more like a dictionary of lists.

Listing 1.90: Code example

1 class Person

52
1.5. Collections and LINQ

2 {
3 public string Name { get ; set ; }
4 public string Department { get ; set ; }
5 }
6

7 class Program
8 {
9 static void Main ()
10 {
11 var people = new List < Person >
12 {
13 new Person { Name = " Alice " , Department = " HR " } ,
14 new Person { Name = " Bob " , Department = " IT " } ,
15 new Person { Name = " Charlie " , Department = " HR " }
16 };
17

18 // Using GroupBy
19 var grouped = people . GroupBy ( p = > p . Department ) ;
20 foreach ( var group in grouped )
21 {
22 Console . WriteLine ( $ " { group . Key }: { string . Join ( " , " , group . Select ( p = >
p . Name ) ) } " ) ;
23 }
24

25 // Using ToLookup
26 var lookup = people . ToLookup ( p = > p . Department ) ;
27 foreach ( var person in lookup [ " HR " ]) // Quick lookup by key
28 {
29 Console . WriteLine ( person . Name ) ; // Outputs : Alice , Charlie
30 }
31 }
32 }

How does ‘Except‘ differ from ‘Distinct‘ in LINQ, and when would you
use each?
• Except: Computes set difference between two sequences.
• Distinct: Removes duplicates within a single sequence.

Listing 1.91: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 var list1 = new List < int > { 1 , 2 , 3 , 4 };

53
1.5. Collections and LINQ

6 var list2 = new List < int > { 3 , 4 , 5 };


7

8 // Using Except : Elements in list1 but not in list2


9 var exceptResult = list1 . Except ( list2 ) ;
10 Console . WriteLine ( string . Join ( " , " , exceptResult ) ) ; // Outputs : 1 , 2
11

12 // Using Distinct : Removes duplicates in a single list


13 var listWithDuplicates = new List < int > { 1 , 2 , 2 , 3 , 4 , 4 };
14 var distinctResult = listWithDuplicates . Distinct () ;
15 Console . WriteLine ( string . Join ( " , " , distinctResult ) ) ; // Outputs : 1 , 2 , 3 ,
4
16 }
17 }

How does ‘Reverse‘ work in LINQ, and what are some practical use cases?
‘Reverse‘ inverts the order of a sequence. Useful for reversing sort orders or processing items
backward.

Listing 1.92: Code example

1 class Program
2 {
3 static void Main ()
4 {
5 var numbers = new List < int > { 1 , 2 , 3 , 4 , 5 };
6

7 // Reverse the sequence


8 var reversedNumbers = numbers . Reverse () ;
9 Console . WriteLine ( string . Join ( " , " , reversedNumbers ) ) ; // Outputs : 5 , 4 ,
3, 2, 1
10 }
11 }

What is the purpose of the ‘OfType‘ method in LINQ, and how does it
differ from ‘Cast‘ ?
• OfType: Filters elements that can be cast to the specified type, excluding invalid ones.
• Cast: Attempts to cast all elements, throwing if any cannot be cast.

Listing 1.93: Code example

1 class Program
2 {
3 static void Main ()
4 {

54
1.5. Collections and LINQ

5 var mixedList = new List < object > { 1 , " string " , 3 , " another string " };
6

7 // OfType : Filters by type and ignores non - matching elements


8 var intResults = mixedList . OfType < int >() ;
9 Console . WriteLine ( string . Join ( " , " , intResults ) ) ; // Outputs : 1 , 3
10

11 // Cast : Attempts to cast every element , throws exception if type mismatch


occurs
12 try
13 {
14 var castResults = mixedList . Cast < int >() ;
15 Console . WriteLine ( string . Join ( " , " , castResults ) ) ; // Throws
InvalidCastException
16 }
17 catch ( InvalidCastException ex )
18 {
19 Console . WriteLine ( ex . Message ) ; // Outputs : Unable to cast object of
type ’ System . String ’ to type ’ System . Int32 ’.
20 }
21 }
22 }

55
2 | OOP Design and Best Practices

Object-oriented programming (OOP) design is foundational for developing robust, maintainable,


and scalable software in C#. It emphasizes organizing code around objects and classes, promoting
clarity, reusability, and modularity. Mastering OOP principles—such as encapsulation, inheri-
tance, and polymorphism—ensures that applications are built on a framework conducive to both
short- and long-term efficiency.

Best practices play a key role in elevating OOP-based solutions to a higher standard of quality.
Principles like SOLID, combined with established design patterns, facilitate streamlined develop-
ment and minimize the risk of technical debt. A thorough understanding of these practices not only
enhances collaboration in team settings but also strengthens an individual’s capacity for designing
reliable architectures.

2.1 Object-Oriented Programming (OOP)


What are the Principles Of OOP?
Encapsulation
Encapsulation is the practice of hiding the internal state of an object and only exposing a controlled
interface. This prevents external code from directly accessing or modifying the internal details of
an object.

Listing 2.1: Code example

1 public class BankAccount


2 {
3 private decimal balance ; // Encapsulation : balance is private
4

5 public void Deposit ( decimal amount )


6 {
7 if ( amount > 0)
8 {
9 balance += amount ;

56
2.1. Object-Oriented Programming (OOP)

10 }
11 }
12

13 public decimal GetBalance ()


14 {
15 return balance ;
16 }
17 }

Inheritance
Inheritance allows a class (child or subclass) to inherit properties and methods from another class
(parent or superclass). It enables code reuse and logical hierarchy within object models.

Listing 2.2: Code example

1 public class Vehicle


2 {
3 public int Speed { get ; set ; }
4

5 public void Drive ()


6 {
7 Console . WriteLine ( " Driving at " + Speed + " km / h " ) ;
8 }
9 }
10

11 public class Car : Vehicle


12 {
13 public string Model { get ; set ; }
14 }

Polymorphism
Polymorphism allows objects of different types to be treated as instances of the same base type. It
is commonly implemented using method overriding (runtime polymorphism) or method overloading
(compile-time polymorphism).

Listing 2.3: Code example

1 public class Animal


2 {
3 public virtual void Speak ()
4 {
5 Console . WriteLine ( " Animal speaks " ) ;
6 }
7 }
8

57
2.1. Object-Oriented Programming (OOP)

9 public class Dog : Animal


10 {
11 public override void Speak ()
12 {
13 Console . WriteLine ( " Dog barks " ) ;
14 }
15 }
16

17 public class Cat : Animal


18 {
19 public override void Speak ()
20 {
21 Console . WriteLine ( " Cat meows " ) ;
22 }
23 }

Abstraction
Abstraction is the concept of hiding the complex implementation details of an object and exposing
only the necessary functionality. It reduces complexity by allowing the programmer to work with
higher-level concepts.

Listing 2.4: Code example

1 public abstract class Shape


2 {
3 public abstract double GetArea () ;
4 }
5

6 public class Circle : Shape


7 {
8 public double Radius { get ; set ; }
9

10 public override double GetArea ()


11 {
12 return Math . PI * Radius * Radius ;
13 }
14 }
15

16 public class Rectangle : Shape


17 {
18 public double Width { get ; set ; }
19 public double Height { get ; set ; }
20

21 public override double GetArea ()


22 {
23 return Width * Height ;

58
2.1. Object-Oriented Programming (OOP)

24 }
25 }

How does inheritance support code reuse in C#?


Inheritance allows a class to inherit methods and properties from a parent class, which promotes
code reuse by allowing subclasses to use and extend functionality without rewriting code.

Listing 2.5: Code example

1 public class Vehicle


2 {
3 public int Speed { get ; set ; }
4 public void Drive ()
5 {
6 Console . WriteLine ( $ " Driving at { Speed } km / h " ) ;
7 }
8 }
9

10 public class Car : Vehicle


11 {
12 public string Model { get ; set ; }
13 }
14

15 public class Program


16 {
17 public static void Main ()
18 {
19 Car car = new Car { Speed = 100 , Model = " Sedan " };
20 car . Drive () ; // Output : Driving at 100 km / h
21 }
22 }

What is the difference between value types and reference types in C#?
• Value types: (e.g., ‘int‘, ‘struct‘) store the actual data directly in the variable. They are
stored on the stack, and each variable has its own copy of the data.
• Reference types: (e.g., ‘class‘, ‘string‘) store a reference to the actual data, which is
allocated on the heap. Multiple variables can refer to the same data.

Listing 2.6: Code example

1 public struct ValueTypeExample


2 {
3 public int X ;
4 }

59
2.1. Object-Oriented Programming (OOP)

6 public class ReferenceTypeExample


7 {
8 public int X ;
9 }
10

11 public class Program


12 {
13 public static void Main ()
14 {
15 // Value Type
16 ValueTypeExample value1 = new ValueTypeExample { X = 10 };
17 ValueTypeExample value2 = value1 ; // Copies the value
18

19 value2 . X = 20;
20 Console . WriteLine ( value1 . X ) ; // Output : 10 ( value1 is not affected by
value2 changes )
21

22 // Reference Type
23 ReferenceTypeExample ref1 = new ReferenceTypeExample { X = 10 };
24 ReferenceTypeExample ref2 = ref1 ; // Both ref1 and ref2 refer to the same
object
25

26 ref2 . X = 20;
27 Console . WriteLine ( ref1 . X ) ; // Output : 20 ( ref1 is affected by ref2
changes )
28 }
29 }

What Is a Class and an Object?


• ‘class‘: describes all the attributes and behaviors (methods) of objects. It is a template or
blueprint for creating objects.
• An ‘object‘: is an instance of a class. It is a basic unit that has attributes (state) and
behavior (methods) defined by its class.

Listing 2.7: Code example

1 public class Car


2 {
3 public string Model { get ; set ; }
4 public int Year { get ; set ; }
5

6 public void Start ()


7 {
8 Console . WriteLine ( " Car is starting ... " ) ;
9 }

60
2.1. Object-Oriented Programming (OOP)

10 }
11

12 public class Program


13 {
14 public static void Main ()
15 {
16 // Creating an object ( instance ) of the Car class
17 Car myCar = new Car { Model = " Toyota " , Year = 2021 };
18 myCar . Start () ; // Output : Car is starting ...
19 }
20 }

What Is an Abstract Class?


An abstract class is a base or parent class that cannot be instantiated directly. It may contain
both abstract methods (without implementation) and concrete methods (with implementation).
Child classes must implement the abstract methods.

Listing 2.8: Code example

1 public abstract class Animal


2 {
3 public abstract void Speak () ; // Abstract method with no implementation
4

5 public void Eat ()


6 {
7 Console . WriteLine ( " Animal is eating ... " ) ;
8 }
9 }
10

11 public class Dog : Animal


12 {
13 public override void Speak () // Must implement the abstract method
14 {
15 Console . WriteLine ( " Dog barks " ) ;
16 }
17 }
18

19 public class Program


20 {
21 public static void Main ()
22 {
23 Dog dog = new Dog () ;
24 dog . Speak () ; // Output : Dog barks
25 dog . Eat () ; // Output : Animal is eating ...
26 }
27 }

61
2.1. Object-Oriented Programming (OOP)

What Are Interfaces?


An interface is a contract that defines methods, properties, and events that a class must imple-
ment. Interfaces contain no implementation. Any class that implements an interface must provide
the implementation of all its members.

Listing 2.9: Code example

1 public interface IAnimal


2 {
3 void Speak () ; // Method signature
4 }
5

6 public class Cat : IAnimal


7 {
8 public void Speak () // Implementation of the interface method
9 {
10 Console . WriteLine ( " Cat meows " ) ;
11 }
12 }
13

14 public class Program


15 {
16 public static void Main ()
17 {
18 IAnimal animal = new Cat () ;
19 animal . Speak () ; // Output : Cat meows
20 }
21 }

What Is the Default Access Modifier of an Interface?


The default access modifier of an interface is internal when no access modifier is explicitly speci-
fied. This means the interface is accessible only within the same assembly unless explicitly marked
as ‘public‘.

What’s the Difference Between Abstract Class and Interface?


• Abstract Class: A base class that can have both abstract (empty) and implemented meth-
ods.
• Interface: A contract that only contains empty methods that must be implemented.
• Abstract Class: is inherited, while Interface is implemented.
• Abstract Class: allows code reuse in inheritance, while Interface enforces a contract
without providing any implementation.

62
2.1. Object-Oriented Programming (OOP)

In What Scenarios Will You Use an Abstract Class and in What Scenarios Will You
Use an Interface?
• Abstract Class: Use it when you need to provide some shared functionality among derived
classes while still allowing some methods to be abstract and defined by subclasses. Example:
A base class ‘Animal‘ that has a method ‘Eat()‘ (implemented) but an abstract method
‘Speak()‘, which can vary between ‘Dog‘ and ‘Cat‘.
• Interface: Use it when you need to enforce a contract between classes but do not want to
provide any implementation details, leaving it up to the classes to define how they fulfill the
contract. Example: An interface ‘IDriveable‘ that must be implemented by both ‘Car‘ and
‘Bicycle‘, with each providing its own implementation of ‘Drive()‘.

Programming Paradigms
What is the primary difference between Functional Programming and Object-Oriented
Programming?
• Functional Programming focuses on pure functions, immutability, and avoiding side ef-
fects. Functions are treated as first-class citizens.
• Object-Oriented Programming (OOP) focuses on objects that encapsulate data and
behavior. It uses principles like inheritance, polymorphism, and encapsulation to structure
code.

How does state management differ between Functional Programming and OOP?
• Functional Programming: State is immutable, meaning it cannot be modified once cre-
ated. New states are returned by functions without changing the original.
• OOP: State is often mutable, and objects hold and modify their internal state over time.

Listing 2.10: Code example

1 // OOP with mutable state


2 public class BankAccount
3 {
4 public int Balance { get ; set ; }
5

6 public void Deposit ( int amount )


7 {
8 Balance += amount ;
9 }
10 }
11

12 // Functional approach with immutable state


13 public class BankAccountFunctional

63
2.1. Object-Oriented Programming (OOP)

14 {
15 public int Balance { get ; }
16

17 public BankAccountFunctional ( int balance )


18 {
19 Balance = balance ;
20 }
21

22 public BankAccountFunctional Deposit ( int amount )


23 {
24 return new BankAccountFunctional ( Balance + amount ) ;
25 }
26 }

What is a pure function in Functional Programming, and how does it contrast with
methods in OOP?
• A pure function always returns the same output given the same input and has no side
effects (it doesn’t alter state outside of the function).
• In OOP, methods often operate on an object’s state and may have side effects.

Listing 2.11: Code example

1 // Pure function example


2 public int Add ( int x , int y )
3 {
4 return x + y ; // No side effects , always returns the same result
5 }
6

7 // OOP method with side effects


8 public class Counter
9 {
10 public int Count { get ; private set ; }
11

12 public void Increment ()


13 {
14 Count ++; // Modifies internal state
15 }
16 }

What role does immutability play in Functional Programming, and how does it com-
pare to OOP?
• Immutability (FP): In Functional Programming, data cannot be changed once it’s created.
To modify something, a new instance must be returned. This prevents side effects.

64
2.1. Object-Oriented Programming (OOP)

• OOP: Objects are often mutable, allowing for state to be modified directly.

Listing 2.12: Code example

1 // OOP with mutable state


2 public class Person
3 {
4 public string Name { get ; set ; }
5

6 public void ChangeName ( string newName )


7 {
8 Name = newName ;
9 }
10 }
11

12 // Functional Programming with immutable state


13 public class ImmutablePerson
14 {
15 public string Name { get ; }
16

17 public ImmutablePerson ( string name )


18 {
19 Name = name ;
20 }
21

22 public ImmutablePerson ChangeName ( string newName )


23 {
24 return new ImmutablePerson ( newName ) ; // Returns new object
25 }
26 }

How do function composition and method inheritance differ between Functional Pro-
gramming and OOP?
• Function Composition (FP): In Functional Programming, small functions are composed
together to build larger, more complex behaviors.
• Inheritance (OOP): Classes inherit methods and properties from parent classes to reuse
functionality and add or override behavior.

Listing 2.13: Code example

1 // Function composition example in Functional Programming


2 public Func < int , int > AddFive = x = > x + 5;
3 public Func < int , int > MultiplyByTwo = x = > x * 2;
4

5 public Func < int , int > CombinedFunction = x = > MultiplyByTwo ( AddFive ( x ) ) ; //
Composition

65
2.1. Object-Oriented Programming (OOP)

7 // Inheritance example in OOP


8 public class Animal
9 {
10 public virtual void Speak () = > Console . WriteLine ( " Animal speaks " ) ;
11 }
12

13 public class Dog : Animal


14 {
15 public override void Speak () = > Console . WriteLine ( " Dog barks " ) ;
16 }

How is polymorphism achieved in Functional Programming versus OOP?


• In OOP, polymorphism is typically achieved through inheritance and interfaces, allowing
objects to be treated as instances of their parent class or interface.
• In Functional Programming, polymorphism is often achieved through higher-order func-
tions, which can accept and return other functions.

Listing 2.14: Code example

1 // Polymorphism in OOP
2 public interface IAnimal
3 {
4 void Speak () ;
5 }
6

7 public class Dog : IAnimal


8 {
9 public void Speak () = > Console . WriteLine ( " Dog barks " ) ;
10 }
11

12 public class Cat : IAnimal


13 {
14 public void Speak () = > Console . WriteLine ( " Cat meows " ) ;
15 }
16

17 public void MakeAnimalSpeak ( IAnimal animal )


18 {
19 animal . Speak () ;
20 }
21

22 // Polymorphism in Functional Programming with higher - order functions


23 public Func < string , string > Shout = x = > x . ToUpper () ;
24 public Func < string , string > Whisper = x = > x . ToLower () ;
25

26 public string Communicate ( Func < string , string > communicationStyle , string message )

66
2.1. Object-Oriented Programming (OOP)

27 {
28 return communicationStyle ( message ) ;
29 }

What is the significance of side effects in Functional Programming, and how does it
differ from OOP?
• Functional Programming: Avoids side effects, meaning functions should not modify any
external state or variables. This makes functions predictable and easier to test.
• OOP: Side effects are common as objects modify their internal state or external environment.

Listing 2.15: Code example

1 // Functional programming without side effects


2 public int Multiply ( int a , int b )
3 {
4 return a * b ; // No side effects , only returns a result
5 }
6

7 // OOP with side effects


8 public class Calculator
9 {
10 public int Total { get ; private set ; }
11

12 public void Add ( int value )


13 {
14 Total += value ; // Modifies internal state ( side effect )
15 }
16 }

How are objects in OOP different from functions in Functional Programming?


• In OOP, objects encapsulate state and behavior, often modifying internal state via methods.
• In Functional Programming, functions are first-class citizens, meaning they can be passed
around and manipulated, but they don’t have internal state.

Listing 2.16: Code example

1 // OOP with objects


2 public class Car
3 {
4 public string Model { get ; set ; }
5

6 public void Drive ()


7 {
8 Console . WriteLine ( $ " { Model } is driving " ) ;

67
2.1. Object-Oriented Programming (OOP)

9 }
10 }
11

12 // Functional Programming with first - class functions


13 public Func < string , string > Drive = model = > $ " { model } is driving " ;

How do concurrency and parallelism differ in Functional Programming compared to


OOP?
• Functional Programming: Concurrency is easier to handle due to immutability, as there
is no shared state between functions.
• OOP: Concurrency often requires managing shared mutable state, which can lead to issues
like race conditions and deadlocks.

Listing 2.17: Code example

1 // Functional Programming avoids shared state


2 public int Calculate ( int a , int b ) = > a + b ; // No shared state , safe for
concurrency
3

4 // OOP with shared mutable state


5 public class Counter
6 {
7 public int Count { get ; set ; }
8

9 public void Increment ()


10 {
11 Count ++;
12 }
13 }

Benefits of Using Functional Programming over OOP


Functional programming offers key advantages over OOP, including:
• Immutability: Data is immutable, preventing side effects and making code more predictable
and easier to test.
• Concurrency: No shared mutable state, simplifying parallelism and concurrency by avoid-
ing race conditions.
• Pure Functions: Functions always return the same result for the same input, improving
reliability and testing.
• Higher-Order Functions: Enables more flexible, modular, and reusable code without
relying on inheritance.
• Declarative Code: Focuses on what to do, leading to more concise and maintainable code.

68
2.2. SOLID Principles

Functional programming is ideal for safe concurrency, easy testing, and modularity, often pro-
viding a cleaner, more maintainable approach than OOP.

Example of Pure Function

Listing 2.18: Code example

1 public int Add ( int x , int y )


2 {
3 return x + y ; // No side effects , same output for same input
4 }

2.2 SOLID Principles


Single Responsibility Principle (SRP) Example
A class should have only one reason to change, meaning it should have only one job or responsibility.
In this example, ‘ReportGenerator‘ is responsible for generating the report, and ‘ReportPrinter‘
is responsible for printing the report. This follows SRP by separating the responsibilities.

Listing 2.19: Code example

1 public class ReportGenerator


2 {
3 public string GenerateReport ()
4 {
5 // Generates the report
6 return " Report Data " ;
7 }
8 }
9

10 public class ReportPrinter


11 {
12 public void Print ( string report )
13 {
14 // Prints the report
15 Console . WriteLine ( report ) ;
16 }
17 }

Open/Closed Principle (OCP) Example


Software entities (classes, modules, functions) should be open for extension, but closed for modi-
fication.

69
2.2. SOLID Principles

Here, the ‘Shape‘ class is open for extension by adding new shapes (like ‘Circle‘ and ‘Rectangle‘),
but the base ‘Shape‘ class itself does not need to be modified.

Listing 2.20: Code example

1 public abstract class Shape


2 {
3 public abstract double Area () ;
4 }
5

6 public class Circle : Shape


7 {
8 public double Radius { get ; set ; }
9

10 public override double Area () = > Math . PI * Radius * Radius ;


11 }
12

13 public class Rectangle : Shape


14 {
15 public double Width { get ; set ; }
16 public double Height { get ; set ; }
17

18 public override double Area () = > Width * Height ;


19 }

Liskov Substitution Principle (LSP) Example


Objects of a superclass should be replaceable with objects of a subclass without affecting the
correctness of the program.
Here, the ‘Ostrich‘ class violates the LSP because it throws an exception when trying to ‘Fly‘.
It is not a proper substitution for the ‘Bird‘ class.

Listing 2.21: Code example

1 public class Bird


2 {
3 public virtual void Fly () { }
4 }
5

6 public class Sparrow : Bird


7 {
8 public override void Fly ()
9 {
10 Console . WriteLine ( " Sparrow is flying " ) ;
11 }
12 }
13

70
2.2. SOLID Principles

14 public class Ostrich : Bird


15 {
16 public override void Fly ()
17 {
18 throw new InvalidOperationException ( " Ostriches can ’t fly " ) ;
19 }
20 }

Interface Segregation Principle (ISP) Example


Clients should not be forced to depend on methods they do not use. Split large interfaces into
smaller, more specific ones.
Here, ‘IPrinter‘ and ‘IScanner‘ are separate interfaces. A simple printer does not need to
implement scanning capabilities, adhering to ISP.

Listing 2.22: Code example

1 public interface IPrinter


2 {
3 void Print () ;
4 }
5

6 public interface IScanner


7 {
8 void Scan () ;
9 }
10

11 public class MultiFunctionPrinter : IPrinter , IScanner


12 {
13 public void Print () { /* Print implementation */ }
14 public void Scan () { /* Scan implementation */ }
15 }
16

17 public class SimplePrinter : IPrinter


18 {
19 public void Print () { /* Print implementation */ }
20 }

Dependency Inversion Principle (DIP) Example


High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend on details. Details should depend on abstractions.
In this example, ‘Notification‘ depends on the ‘IMessageSender‘ abstraction, not a specific
implementation like ‘EmailSender‘, which follows DIP.

71
2.3. DRY (Don’t Repeat Yourself)

Listing 2.23: Code example

1 public interface IMessageSender


2 {
3 void SendMessage ( string message ) ;
4 }
5

6 public class EmailSender : IMessageSender


7 {
8 public void SendMessage ( string message )
9 {
10 Console . WriteLine ( $ " Sending email : { message } " ) ;
11 }
12 }
13

14 public class Notification


15 {
16 private readonly IMessageSender _messageSender ;
17

18 public Notification ( IMessageSender messageSender )


19 {
20 _messageSender = messageSender ;
21 }
22

23 public void Notify ( string message )


24 {
25 _messageSender . SendMessage ( message ) ;
26 }
27 }
28

29 public class Program


30 {
31 public static void Main ()
32 {
33 IMessageSender sender = new EmailSender () ;
34 Notification notification = new Notification ( sender ) ;
35 notification . Notify ( " Hello , World ! " ) ;
36 }
37 }

2.3 DRY (Don’t Repeat Yourself)


How does the DRY principle help in maintaining code?
The DRY principle helps in reducing code duplication by abstracting common functionality into
reusable methods or classes, thus making code easier to maintain and modify.

72
2.4. Clean Code and Best Practices

Listing 2.24: Code example

1 public class Employee


2 {
3 public string Name { get ; set ; }
4 public int Age { get ; set ; }
5

6 public void PrintDetails ()


7 {
8 Console . WriteLine ( $ " { Name } , { Age } " ) ;
9 }
10 }
11

12 public class Manager : Employee


13 {
14 public string Department { get ; set ; }
15

16 public void PrintManagerDetails ()


17 {
18 PrintDetails () ; // DRY principle in action
19 Console . WriteLine ( $ " Department : { Department } " ) ;
20 }
21 }
22

23 public class Program


24 {
25 public static void Main ()
26 {
27 Manager manager = new Manager { Name = " Alice " , Age = 40 , Department = " HR
" };
28 manager . PrintManagerDetails () ;
29 }
30 }

2.4 Clean Code and Best Practices


How does the Single Responsibility Principle (SRP) help in writing clean code, and
how would you apply it in C#?
The Single Responsibility Principle (SRP) states that a class should have only one reason to
change, meaning it should have only one job or responsibility. Following SRP ensures that classes
are small, modular, and easier to maintain, test, and extend.

Listing 2.25: Code example

1 class Invoice

73
2.4. Clean Code and Best Practices

2 {
3 public void CalculateTotal () { /* Business logic */ }
4

5 // This violates SRP : sending emails is a different responsibility


6 public void SendEmail () { /* Email sending logic */ }
7 }
8

9 // Correct approach : Separate responsibilities into distinct classes


10 class Invoice
11 {
12 public void CalculateTotal () { /* Business logic */ }
13 }
14

15 class EmailSender
16 {
17 public void SendEmail () { /* Email sending logic */ }
18 }

What is the Open/Closed Principle (OCP), and how do you implement it in C#?
The Open/Closed Principle (OCP) states that a class should be open for extension but closed for
modification. This means that the behavior of a class should be extendable without modifying its
source code.

Listing 2.26: Code example

1 public abstract class Shape


2 {
3 public abstract double CalculateArea () ;
4 }
5

6 // New shapes can extend the behavior without modifying the original code
7 public class Circle : Shape
8 {
9 public double Radius { get ; set ; }
10 public override double CalculateArea () = > Math . PI * Radius * Radius ;
11 }
12

13 public class Rectangle : Shape


14 {
15 public double Width { get ; set ; }
16 public double Height { get ; set ; }
17 public override double CalculateArea () = > Width * Height ;
18 }

74
2.4. Clean Code and Best Practices

What is the Liskov Substitution Principle (LSP), and how would you ensure it is
followed in C#?
The Liskov Substitution Principle (LSP) states that derived classes should be substitutable for
their base classes without affecting the correctness of the program. This means that derived
classes must not break the expectations set by the base class.

Listing 2.27: Code example

1 public class Bird


2 {
3 public virtual void Fly () { Console . WriteLine ( " Flying " ) ; }
4 }
5

6 public class Penguin : Bird


7 {
8 // Violates LSP because Penguins can ’t fly
9 public override void Fly () { throw new NotImplementedException () ; }
10 }
11

12 // Correct approach : Use a more specific hierarchy


13 public class Bird
14 {
15 public virtual void Move () { Console . WriteLine ( " Moving " ) ; }
16 }
17

18 public class FlyingBird : Bird


19 {
20 public virtual void Fly () { Console . WriteLine ( " Flying " ) ; }
21 }
22

23 public class Penguin : Bird


24 {
25 public override void Move () { Console . WriteLine ( " Swimming " ) ; }
26 }

How do you apply the Interface Segregation Principle (ISP) in C#, and why is it
important?
The Interface Segregation Principle (ISP) states that clients should not be forced to depend on
interfaces they do not use. Instead of creating large, monolithic interfaces, create smaller, more
specific interfaces.

Listing 2.28: Code example

1 // Violates ISP : Interface is too large , forcing all implementations to provide


unnecessary methods

75
2.4. Clean Code and Best Practices

2 public interface IWorker


3 {
4 void Work () ;
5 void Eat () ;
6 }
7

8 // Correct approach : Split into smaller , focused interfaces


9 public interface IWorker
10 {
11 void Work () ;
12 }
13

14 public interface IFeedable


15 {
16 void Eat () ;
17 }
18

19 public class Human : IWorker , IFeedable


20 {
21 public void Work () { /* Work logic */ }
22 public void Eat () { /* Eat logic */ }
23 }
24

25 public class Robot : IWorker


26 {
27 public void Work () { /* Work logic */ }
28 }

What is the Dependency Inversion Principle (DIP), and how would you implement it
in C#?
The Dependency Inversion Principle (DIP) states that high-level modules should not depend on
low-level modules. Both should depend on abstractions. This reduces the coupling between mod-
ules and makes the system more flexible and maintainable.

Listing 2.29: Code example

1 // Violates DIP : High - level module depends on low - level module


2 public class Light
3 {
4 public void TurnOn () { /* Logic to turn on the light */ }
5 }
6

7 public class Switch


8 {
9 private Light _light ;
10 public Switch ( Light light ) { _light = light ; }

76
2.4. Clean Code and Best Practices

11

12 public void Operate () { _light . TurnOn () ; }


13 }
14

15 // Correct approach : High - level module depends on an abstraction


16 public interface IDevice
17 {
18 void Operate () ;
19 }
20

21 public class Light : IDevice


22 {
23 public void Operate () { /* Logic to operate the light */ }
24 }
25

26 public class Switch


27 {
28 private IDevice _device ;
29 public Switch ( IDevice device ) { _device = device ; }
30

31 public void Operate () { _device . Operate () ; }


32 }

How can you use dependency injection to improve code testability and maintainabil-
ity?
Dependency injection allows you to inject dependencies into a class rather than having the class in-
stantiate them directly. This improves testability by allowing you to mock or replace dependencies
in tests, and it improves maintainability by decoupling classes from specific implementations.

Listing 2.30: Code example

1 public interface ILogger


2 {
3 void Log ( string message ) ;
4 }
5

6 public class ConsoleLogger : ILogger


7 {
8 public void Log ( string message ) { Console . WriteLine ( message ) ; }
9 }
10

11 public class Service


12 {
13 private readonly ILogger _logger ;
14

15 // Dependency is injected via the constructor

77
2.4. Clean Code and Best Practices

16 public Service ( ILogger logger )


17 {
18 _logger = logger ;
19 }
20

21 public void Execute ()


22 {
23 _logger . Log ( " Executing service ... " ) ;
24 }
25 }
26

27 // Unit test : You can mock ILogger for testing


28 public class MockLogger : ILogger
29 {
30 public void Log ( string message ) { /* Mock log behavior */ }
31 }

What are code smells, and how would you refactor a method that is too long?
Code smells are indicators of potential issues in the code that may lead to problems such as
maintainability challenges, poor readability, and bugs. A method that is too long is a common
code smell. Refactoring techniques such as Extract Method can be used to break a long method
into smaller, more manageable methods.

Listing 2.31: Code example

1 // Code smell : Long method


2 public class OrderProcessor
3 {
4 public void ProcessOrder ( Order order )
5 {
6 ValidateOrder ( order ) ;
7 CalculateShipping ( order ) ;
8 ProcessPayment ( order ) ;
9 GenerateInvoice ( order ) ;
10 SendEmailConfirmation ( order ) ;
11 }
12 }
13

14 // Refactored : Extract Method


15 public class OrderProcessor
16 {
17 public void ProcessOrder ( Order order )
18 {
19 ValidateOrder ( order ) ;
20 CalculateShipping ( order ) ;
21 ProcessPayment ( order ) ;

78
2.4. Clean Code and Best Practices

22 GenerateInvoice ( order ) ;
23 SendEmailConfirmation ( order ) ;
24 }
25

26 private void ValidateOrder ( Order order ) { /* Logic */ }


27 private void CalculateShipping ( Order order ) { /* Logic */ }
28 private void ProcessPayment ( Order order ) { /* Logic */ }
29 private void GenerateInvoice ( Order order ) { /* Logic */ }
30 private void SendEmailConfirmation ( Order order ) { /* Logic */ }
31 }

How would you handle exceptions in a clean and efficient way in C#?
Exceptions should be caught and handled at appropriate levels. You should avoid catching generic
‘Exception‘ unless necessary and handle specific exceptions to make the error handling clearer.
Use ‘try-catch-finally‘ blocks wisely and avoid swallowing exceptions silently.

Listing 2.32: Code example

1 public class FileProcessor


2 {
3 public void ProcessFile ( string path )
4 {
5 try
6 {
7 // Try to open and process the file
8 string content = File . ReadAllText ( path ) ;
9 Console . WriteLine ( content ) ;
10 }
11 catch ( FileNotFoundException ex )
12 {
13 // Specific exception handling
14 Console . WriteLine ( $ " File not found : { ex . Message } " ) ;
15 }
16 catch ( UnauthorizedAccessException ex )
17 {
18 // Handle access issues
19 Console . WriteLine ( $ " Access denied : { ex . Message } " ) ;
20 }
21 finally
22 {
23 Console . WriteLine ( " Finished file processing . " ) ;
24 }
25 }
26 }

79
2.4. Clean Code and Best Practices

How can you use meaningful names in variables, methods, and classes to improve
code readability?
Meaningful names should clearly describe the purpose of variables, methods, and classes. Avoid
short, ambiguous names and strive to use names that reflect the function or behavior of the code.
This improves readability and maintainability.

Listing 2.33: Code example

1 // Poor naming
2 int x = 10;
3 string y = " John Doe " ;
4

5 // Meaningful naming
6 int userId = 10;
7 string userName = " John Doe " ;

Why should you avoid using magic numbers in your code, and how would you refactor
them?
Magic numbers are hard-coded values that have no context or meaning, making the code difficult to
understand. They should be replaced with named constants or enumerations that convey meaning.

Listing 2.34: Code example

1 // Code smell : Magic numbers


2 public class Game
3 {
4 public void SetPlayerHealth ( int health )
5 {
6 if ( health < 0 || health > 100)
7 {
8 throw new ArgumentOutOfRangeException ( " Health must be between 0 and
100. " ) ;
9 }
10 }
11 }
12

13 // Refactored : Replace magic numbers with named constants


14 public class Game
15 {
16 private const int MinHealth = 0;
17 private const int MaxHealth = 100;
18

19 public void SetPlayerHealth ( int health )


20 {
21 if ( health < MinHealth || health > MaxHealth )

80
2.4. Clean Code and Best Practices

22 {
23 throw new ArgumentOutOfRangeException ( $ " Health must be between {
MinHealth } and { MaxHealth }. " ) ;
24 }
25 }
26 }

How would you avoid code duplication in your project, and why is it important?
Code duplication leads to redundant code, which makes maintenance harder, increases the likeli-
hood of bugs, and results in inconsistencies. Code should be refactored to remove duplication by
using methods, inheritance, composition, or other design patterns.

Listing 2.35: Code example

1 // Code smell : Duplicated code


2 public class Circle
3 {
4 public double CalculateArea ( double radius ) = > Math . PI * radius * radius ;
5 }
6

7 public class Square


8 {
9 public double CalculateArea ( double side ) = > side * side ;
10 }
11

12 // Refactored : Remove duplication


13 public interface IShape
14 {
15 double CalculateArea () ;
16 }
17

18 public class Circle : IShape


19 {
20 public double Radius { get ; set ; }
21 public double CalculateArea () = > Math . PI * Radius * Radius ;
22 }
23

24 public class Square : IShape


25 {
26 public double Side { get ; set ; }
27 public double CalculateArea () = > Side * Side ;
28 }

81
2.4. Clean Code and Best Practices

What are guard clauses, and why are they useful in keeping your code clean?
Guard clauses are simple ‘if‘ statements that check for invalid conditions at the beginning of a
method. They help avoid deeply nested ‘if-else‘ blocks by returning early when the method’s
requirements aren’t met.

Listing 2.36: Code example

1 // Without guard clause : Nested ifs


2 public void ProcessOrder ( Order order )
3 {
4 if ( order != null )
5 {
6 if ( order . IsValid )
7 {
8 // Process order
9 }
10 }
11 }
12

13 // With guard clause : Early return


14 public void ProcessOrder ( Order order )
15 {
16 if ( order == null || ! order . IsValid )
17 {
18 return ;
19 }
20

21 // Process order
22 }

How do you ensure clean separation of concerns in your code?


Separation of concerns means breaking down the code into distinct sections, each responsible for
a specific aspect of the application. This is achieved by using layers (e.g., UI, business logic, data
access) and applying design patterns such as MVC or repository pattern.

Listing 2.37: Code example

1 // Separation of concerns using layers


2 public class OrderService
3 {
4 private readonly OrderRepository _repository ;
5 public OrderService ( OrderRepository repository )
6 {
7 _repository = repository ;
8 }
9

82
2.4. Clean Code and Best Practices

10 public void ProcessOrder ( Order order )


11 {
12 if ( order . IsValid )
13 {
14 _repository . Save ( order ) ;
15 }
16 }
17 }
18

19 public class OrderRepository


20 {
21 public void Save ( Order order )
22 {
23 // Logic for saving the order to the database
24 }
25 }

Why should you avoid side effects in methods, and how do you refactor code to prevent
them?
Side effects occur when a method modifies state outside its own scope, such as altering global vari-
ables or changing input parameters. This can lead to unpredictable behavior and make debugging
difficult. Refactoring methods to be pure functions, which return the same output for the same
input and do not change external state, helps avoid side effects.

Listing 2.38: Code example

1 // Code with side effect : Mutating external state


2 public class Counter
3 {
4 public int Count = 0;
5

6 public void Increment ( int value )


7 {
8 Count += value ; // Side effect : Modifying external state
9 }
10 }
11

12 // Refactored : Avoid side effects


13 public class Counter
14 {
15 public int Increment ( int currentCount , int value )
16 {
17 return currentCount + value ; // Pure function
18 }
19 }

83
2.4. Clean Code and Best Practices

How do you handle large methods with multiple responsibilities, and how do you
refactor them?
Large methods with multiple responsibilities should be refactored using the Extract Method refac-
toring pattern. Each responsibility should be moved into its own method, making the code more
modular and easier to test.

Listing 2.39: Code example

1 // Large method with multiple responsibilities


2 public void ProcessOrder ( Order order )
3 {
4 ValidateOrder ( order ) ;
5 CalculateTotal ( order ) ;
6 ProcessPayment ( order ) ;
7 GenerateInvoice ( order ) ;
8 }
9

10 // Refactored : Extract Method


11 public void ProcessOrder ( Order order )
12 {
13 ValidateOrder ( order ) ;
14 CalculateOrder ( order ) ;
15 }
16

17 private void ValidateOrder ( Order order ) { /* Validation logic */ }


18 private void CalculateTotal ( Order order ) { /* Calculation logic */ }
19 private void ProcessPayment ( Order order ) { /* Payment processing logic */ }
20 private void GenerateInvoice ( Order order ) { /* Invoice generation logic */ }

Why should you favor composition over inheritance in OOP, and how do you apply
this in C#?
Composition allows you to build complex functionality by composing objects together, promoting
flexibility and reusability. Inheritance, on the other hand, creates tight coupling between classes,
making it harder to modify or extend behavior.

Listing 2.40: Code example

1 // Inheritance : Less flexible , tight coupling


2 public class Car
3 {
4 public void Drive () { /* Drive logic */ }
5 }
6

7 public class ElectricCar : Car


8 {

84
2.4. Clean Code and Best Practices

9 public void ChargeBattery () { /* Battery charging logic */ }


10 }
11

12 // Composition : More flexible


13 public class Car
14 {
15 private readonly Engine _engine ;
16 public Car ( Engine engine )
17 {
18 _engine = engine ;
19 }
20

21 public void Drive () { _engine . Start () ; }


22 }
23

24 public class Engine


25 {
26 public void Start () { /* Engine starting logic */ }
27 }

How do you ensure your code follows the DRY (Don’t Repeat Yourself ) principle?
The DRY principle promotes reusability by avoiding duplication of logic and code. Repeated code
should be extracted into reusable methods, classes, or components. This reduces the maintenance
burden and minimizes bugs.

Listing 2.41: Code example

1 // Violating DRY : Repeated code


2 public class Rectangle
3 {
4 public double CalculateArea ( double width , double height )
5 {
6 return width * height ;
7 }
8

9 public double CalculatePerimeter ( double width , double height )


10 {
11 return 2 * ( width + height ) ;
12 }
13 }
14

15 // Following DRY : Reusable logic


16 public class Rectangle
17 {
18 private readonly double _width ;
19 private readonly double _height ;

85
2.4. Clean Code and Best Practices

20

21 public Rectangle ( double width , double height )


22 {
23 _width = width ;
24 _height = height ;
25 }
26

27 public double CalculateArea () = > _width * _height ;


28 public double CalculatePerimeter () = > 2 * ( _width + _height ) ;
29 }

How can you apply immutability in C#, and why is it beneficial for clean code?
Immutability means that an object’s state cannot be changed after it is created. Applying im-
mutability reduces side effects, makes your code more predictable, and simplifies debugging.

Listing 2.42: Code example

1 public class ImmutablePerson


2 {
3 public string Name { get ; }
4 public int Age { get ; }
5

6 public ImmutablePerson ( string name , int age )


7 {
8 Name = name ;
9 Age = age ;
10 }
11 }
12

13 // This class is immutable ; once an instance is created , its state cannot be


changed
14 var person = new ImmutablePerson ( " John " , 30) ;
15 // person . Name = " Doe "; // Compile - time error : Read - only property

How can you refactor tightly coupled code to follow clean code principles?
Tightly coupled code can be refactored by introducing interfaces or abstractions to decouple depen-
dencies. Dependency injection is a common approach to break tight coupling, allowing components
to interact through abstractions rather than concrete implementations.

Listing 2.43: Code example

1 // Tightly coupled code


2 public class Car
3 {
4 private readonly GasEngine _engine ;

86
2.5. Incremental Refactor and Code Update Techniques

5 public Car ()
6 {
7 _engine = new GasEngine () ; // Car is tightly coupled to GasEngine
8 }
9

10 public void Drive () { _engine . Start () ; }


11 }
12

13 // Refactored : Loosely coupled with dependency injection


14 public interface IEngine
15 {
16 void Start () ;
17 }
18

19 public class GasEngine : IEngine


20 {
21 public void Start () { /* Logic for starting gas engine */ }
22 }
23

24 public class Car


25 {
26 private readonly IEngine _engine ;
27

28 public Car ( IEngine engine )


29 {
30 _engine = engine ; // Car is loosely coupled to IEngine
31 }
32

33 public void Drive () { _engine . Start () ; }


34 }

2.5 Incremental Refactor and Code Update Techniques


What is incremental refactoring, and why is it important when working with legacy
codebases?
Incremental refactoring is the process of improving and restructuring code in small, manageable
steps rather than performing large-scale refactoring all at once. This approach allows you to
continuously improve the code while minimizing risk and keeping the codebase functional at all
times. It is especially important in legacy systems where large changes could introduce unexpected
bugs.

Listing 2.44: Code example

1 // Example of a long method in legacy code

87
2.5. Incremental Refactor and Code Update Techniques

2 public class OrderProcessor


3 {
4 public void ProcessOrder ( Order order )
5 {
6 // Multiple responsibilities mixed together
7 ValidateOrder ( order ) ;
8 ApplyDiscounts ( order ) ;
9 SendConfirmationEmail ( order ) ;
10 }
11 }
12

13 // Incremental refactoring : First , separate concerns


14 public class OrderProcessor
15 {
16 public void ProcessOrder ( Order order )
17 {
18 ValidateOrder ( order ) ; // Responsibility : Validation
19 ApplyDiscounts ( order ) ; // Responsibility : Discounts
20 SendConfirmationEmail ( order ) ; // Responsibility : Notifications
21 }
22 }
23

24 // Next , move each responsibility into separate classes as needed

How does the Extract Method refactoring technique help in incremental updates?
The Extract Method refactoring technique involves identifying pieces of functionality within a
method and moving them into a new method with a meaningful name. This makes the code more
modular, easier to understand, and reusable. It also enables incremental improvements by allowing
small, isolated changes.

Listing 2.45: Code example

1 // Before refactoring : Long method


2 public void GenerateInvoice ( Order order )
3 {
4 // Several responsibilities handled within the same method
5 ValidateOrder ( order ) ;
6 CalculateTotal ( order ) ;
7 PrintInvoice ( order ) ;
8 }
9

10 // After refactoring : Extract Method


11 public void GenerateInvoice ( Order order )
12 {
13 ValidateOrder ( order ) ; // Extracted logic
14 CalculateTotal ( order ) ; // Extracted logic

88
2.5. Incremental Refactor and Code Update Techniques

15 PrintInvoice ( order ) ; // Extracted logic


16 }
17

18 private void ValidateOrder ( Order order ) { /* Logic */ }


19 private void CalculateTotal ( Order order ) { /* Logic */ }
20 private void PrintInvoice ( Order order ) { /* Logic */ }

How can you apply the Introduce Parameter Object technique in C# during incre-
mental refactoring?
The Introduce Parameter Object refactoring consolidates multiple parameters that are frequently
passed together into a single object. This simplifies method signatures, reduces duplication, and
makes code more maintainable.

Listing 2.46: Code example

1 // Before refactoring : Multiple related parameters


2 public void ProcessOrder ( string customerName , string address , string email , int
orderId )
3 {
4 // Logic using all parameters
5 }
6

7 // After refactoring : Introduce Parameter Object


8 public class OrderInfo
9 {
10 public string CustomerName { get ; set ; }
11 public string Address { get ; set ; }
12 public string Email { get ; set ; }
13 public int OrderId { get ; set ; }
14 }
15

16 public void ProcessOrder ( OrderInfo orderInfo )


17 {
18 // Use orderInfo object instead of individual parameters
19 }

What is the Replace Conditional with Polymorphism refactoring, and how does it
improve code quality?
Replace Conditional with Polymorphism is a refactoring technique where conditional logic (such
as ‘if‘ or ‘switch‘ statements) is replaced with polymorphic method calls. This reduces complex
conditional logic and makes the code more modular and maintainable.

Listing 2.47: Code example

89
2.5. Incremental Refactor and Code Update Techniques

1 // Before refactoring : Conditional logic


2 public class ShippingService
3 {
4 public double CalculateShippingCost ( Order order )
5 {
6 if ( order . Type == " Express " )
7 {
8 return 50.0;
9 }
10 else if ( order . Type == " Standard " )
11 {
12 return 20.0;
13 }
14 return 0.0;
15 }
16 }
17

18 // After refactoring : Replace Conditional with Polymorphism


19 public abstract class Shipping
20 {
21 public abstract double CalculateShippingCost () ;
22 }
23

24 public class ExpressShipping : Shipping


25 {
26 public override double CalculateShippingCost () = > 50.0;
27 }
28

29 public class StandardShipping : Shipping


30 {
31 public override double CalculateShippingCost () = > 20.0;
32 }

How does the Replace Magic Numbers with Constants technique improve code read-
ability?
Replace Magic Numbers with Constants involves replacing hardcoded numeric values (magic num-
bers) with named constants. This improves code readability by making it clear what the numbers
represent and allows easier maintenance if the values need to change.

Listing 2.48: Code example

1 // Before refactoring : Magic numbers


2 public class Game
3 {
4 public void SetPlayerHealth ( int health )
5 {

90
2.5. Incremental Refactor and Code Update Techniques

6 if ( health < 0 || health > 100)


7 {
8 throw new ArgumentOutOfRangeException ( " Health must be between 0 and
100. " ) ;
9 }
10 }
11 }
12

13 // After refactoring : Use constants for better clarity


14 public class Game
15 {
16 private const int MinHealth = 0;
17 private const int MaxHealth = 100;
18

19 public void SetPlayerHealth ( int health )


20 {
21 if ( health < MinHealth || health > MaxHealth )
22 {
23 throw new ArgumentOutOfRangeException ( $ " Health must be between {
MinHealth } and { MaxHealth }. " ) ;
24 }
25 }
26 }

What is the Inline Method refactoring technique, and when would you apply it?
The Inline Method refactoring technique involves replacing a method call with the method’s body,
effectively removing the method. This is useful when a method is very simple and does not add
any abstraction, making the code more readable by reducing unnecessary indirection.

Listing 2.49: Code example

1 // Before refactoring : Simple method that adds little value


2 public class Order
3 {
4 public bool IsEmpty () = > Items . Count == 0;
5 }
6

7 public class OrderProcessor


8 {
9 public void ProcessOrder ( Order order )
10 {
11 if ( order . IsEmpty () )
12 {
13 Console . WriteLine ( " No items to process . " ) ;
14 }
15 }

91
2.5. Incremental Refactor and Code Update Techniques

16 }
17

18 // After refactoring : Inline the simple method


19 public class OrderProcessor
20 {
21 public void ProcessOrder ( Order order )
22 {
23 if ( order . Items . Count == 0)
24 {
25 Console . WriteLine ( " No items to process . " ) ;
26 }
27 }
28 }

How can the Move Method refactoring improve class design during incremental up-
dates?
The Move Method refactoring involves moving a method from one class to another when it is
more closely related to the responsibilities of the target class. This helps in reducing coupling and
improving the cohesion of classes.
Listing 2.50: Code example

1 // Before refactoring : Method in wrong class


2 public class Customer
3 {
4 public void CalculateOrderTotal ( Order order )
5 {
6 // Logic to calculate total for the order
7 }
8 }
9

10 // After refactoring : Move Method to the Order class


11 public class Order
12 {
13 public void CalculateTotal ()
14 {
15 // Logic to calculate total for the order
16 }
17 }

How would you apply the Encapsulate Field refactoring technique in C#?
Encapsulate Field involves making a field private and providing public getter and/or setter methods
to control access. This helps in maintaining control over the field’s value and allows adding
validation or additional logic when accessing or modifying it.

92
2.5. Incremental Refactor and Code Update Techniques

Listing 2.51: Code example

1 // Before refactoring : Public field


2 public class Customer
3 {
4 public string Name ;
5 }
6

7 // After refactoring : Encapsulate the field with properties


8 public class Customer
9 {
10 private string _name ;
11

12 public string Name


13 {
14 get = > _name ;
15 set = > _name = value ;
16 }
17 }

How does Introduce Null Object help to simplify conditional logic in C#?
Introduce Null Object is a refactoring technique that replaces null checks with a special Null Object
that provides default behavior. This reduces the need for repetitive null checks and simplifies the
code by treating the null object as a real object.

Listing 2.52: Code example

1 // Before refactoring : Null checks everywhere


2 public class OrderProcessor
3 {
4 public void ProcessOrder ( Order order )
5 {
6 if ( order != null )
7 {
8 // Process order
9 }
10 }
11 }
12

13 // After refactoring : Null Object pattern


14 public class NullOrder : Order
15 {
16 public override void Process () { /* No - op */ }
17 }
18

19 public class OrderProcessor


20 {

93
2.5. Incremental Refactor and Code Update Techniques

21 public void ProcessOrder ( Order order )


22 {
23 order . Process () ; // No null check needed
24 }
25 }
26

27 // Null Object pattern usage


28 Order order = GetOrder () ?? new NullOrder () ;
29 orderProcessor . ProcessOrder ( order ) ;

How can you apply the Replace Constructor with Factory Method refactoring tech-
nique in C#?
Replace Constructor with Factory Method replaces direct constructor calls with a factory method
that centralizes the creation logic. This provides more flexibility and allows for complex instanti-
ation logic, especially when deciding between different subclasses.

Listing 2.53: Code example

1 // Before refactoring : Direct constructor calls


2 public class Customer
3 {
4 public Customer ( string name ) { /* Constructor logic */ }
5 }
6

7 // After refactoring : Factory method to centralize creation logic


8 public class CustomerFactory
9 {
10 public static Customer Create ( string name )
11 {
12 return new Customer ( name ) ;
13 }
14 }

How does the Split Temporary Variable refactoring help in improving code clarity?
The Split Temporary Variable refactoring involves creating separate variables for each purpose
rather than reusing a single variable for multiple purposes. This improves clarity by ensuring each
variable has a clear and consistent role in the code.

Listing 2.54: Code example

1 // Before refactoring : Reusing a temporary variable


2 public class Calculator
3 {
4 public double Calculate ( double radius , double height )

94
2.5. Incremental Refactor and Code Update Techniques

5 {
6 double result = radius * radius * Math . PI ;
7 result = height * result ; // Reusing the result variable
8 return result ;
9 }
10 }
11

12 // After refactoring : Split into separate variables


13 public class Calculator
14 {
15 public double Calculate ( double radius , double height )
16 {
17 double baseArea = radius * radius * Math . PI ;
18 double volume = height * baseArea ;
19 return volume ;
20 }
21 }

How does Replace Nested Conditional with Guard Clauses simplify complex methods?
Replace Nested Conditional with Guard Clauses refactoring simplifies a method by handling ex-
ceptional or edge cases at the beginning, allowing the main logic to flow naturally without deeply
nested ‘if-else‘ blocks. This improves readability and reduces cyclomatic complexity.

Listing 2.55: Code example

1 // Before refactoring : Nested conditionals


2 public void ProcessOrder ( Order order )
3 {
4 if ( order != null )
5 {
6 if ( order . IsValid )
7 {
8 // Process order
9 }
10 }
11 }
12

13 // After refactoring : Use guard clauses


14 public void ProcessOrder ( Order order )
15 {
16 if ( order == null ) return ;
17 if (! order . IsValid ) return ;
18

19 // Process order
20 }

95
2.5. Incremental Refactor and Code Update Techniques

What is the Replace Loop with LINQ refactoring technique in C#, and why is it
useful?
Replace Loop with LINQ involves replacing manual loops with LINQ queries to simplify the code
and make it more declarative. This improves readability and reduces the likelihood of errors related
to loop control.
Listing 2.56: Code example

1 // Before refactoring : Manual loop


2 public int Sum ( IEnumerable < int > numbers )
3 {
4 int sum = 0;
5 foreach ( var number in numbers )
6 {
7 sum += number ;
8 }
9 return sum ;
10 }
11

12 // After refactoring : Use LINQ


13 public int Sum ( IEnumerable < int > numbers )
14 {
15 return numbers . Sum () ;
16 }

How does the Introduce Explaining Variable refactoring improve code comprehension?
The Introduce Explaining Variable refactoring introduces a new variable with a meaningful name
to explain a complex expression or piece of logic. This improves code readability by making the
purpose of the expression clear.
Listing 2.57: Code example

1 // Before refactoring : Complex expression


2 public bool IsEligibleForDiscount ( Order order )
3 {
4 return ( order . TotalAmount > 1000 && order . Customer . HasLoyaltyCard ) ;
5 }
6

7 // After refactoring : Introduce explaining variables


8 public bool IsEligibleForDiscount ( Order order )
9 {
10 bool isLargeOrder = order . TotalAmount > 1000;
11 bool hasLoyaltyCard = order . Customer . HasLoyaltyCard ;
12

13 return isLargeOrder && hasLoyaltyCard ;


14 }

96
2.5. Incremental Refactor and Code Update Techniques

How does Extract Class help in managing large classes during refactoring?
Extract Class involves breaking down a large class that handles multiple responsibilities into
smaller, more focused classes. This improves cohesion and makes the code easier to maintain
and test.

Listing 2.58: Code example

1 // Before refactoring : Large class with multiple responsibilities


2 public class OrderProcessor
3 {
4 public void ValidateOrder ( Order order ) { /* Validation logic */ }
5 public void ProcessPayment ( Order order ) { /* Payment logic */ }
6 public void SendNotification ( Order order ) { /* Notification logic */ }
7 }
8

9 // After refactoring : Extract Class


10 public class OrderValidator
11 {
12 public void Validate ( Order order ) { /* Validation logic */ }
13 }
14

15 public class PaymentProcessor


16 {
17 public void Process ( Order order ) { /* Payment logic */ }
18 }
19

20 public class NotificationService


21 {
22 public void SendNotification ( Order order ) { /* Notification logic */ }
23 }

How does the Introduce Assertion refactoring improve code robustness?


Introduce Assertion adds assertions in code to explicitly state assumptions about inputs, outputs,
or invariants. This helps catch bugs early by making the developer’s expectations explicit and
verifiable.

Listing 2.59: Code example

1 public void ProcessOrder ( Order order )


2 {
3 // Before refactoring : No assertions
4 if ( order == null ) throw new ArgumentNullException () ;
5

6 // After refactoring : Introduce assertion


7 Debug . Assert ( order != null , " Order must not be null " ) ;
8

97
2.5. Incremental Refactor and Code Update Techniques

9 // Process the order


10 }

What is the Collapse Hierarchy refactoring technique, and when should it be applied?
Collapse Hierarchy involves merging a class hierarchy when the child class does not add any
significant functionality beyond the parent class. This simplifies the code and reduces unnecessary
abstraction.

Listing 2.60: Code example

1 // Before refactoring : Unnecessary hierarchy


2 public class Employee
3 {
4 public string Name { get ; set ; }
5 }
6

7 public class Manager : Employee


8 {
9 // No additional functionality
10 }
11

12 // After refactoring : Collapse hierarchy


13 public class Employee
14 {
15 public string Name { get ; set ; }
16 }

How does the Remove Dead Code refactoring technique help in maintaining a clean
codebase?
Remove Dead Code involves identifying and deleting code that is no longer used or executed.
Keeping dead code in a codebase increases complexity, makes it harder to maintain, and can
introduce bugs if accidentally invoked.

Listing 2.61: Code example

1 // Before refactoring : Dead code


2 public void ProcessOrder ( Order order )
3 {
4 if ( order != null )
5 {
6 // Process order
7 }
8 }
9

98
2.5. Incremental Refactor and Code Update Techniques

10 // After refactoring : Removed dead code


11 public void ProcessOrder ( Order order )
12 {
13 if ( order == null ) return ;
14

15 // Process order
16 }

How do you apply the Simplify Method Signature refactoring technique in C#?
Simplify Method Signature refactoring involves reducing the complexity of method signatures by
removing unnecessary parameters or combining multiple parameters into a single object. This
makes the code easier to read and maintain.

Listing 2.62: Code example

1 // Before refactoring : Complex method signature with too many parameters


2 public void CreateOrder ( string customerName , string address , string email , string
phoneNumber )
3 {
4 // Logic to create order
5 }
6

7 // After refactoring : Simplified by using a parameter object


8 public class CustomerInfo
9 {
10 public string Name { get ; set ; }
11 public string Address { get ; set ; }
12 public string Email { get ; set ; }
13 public string PhoneNumber { get ; set ; }
14 }
15

16 public void CreateOrder ( CustomerInfo customer )


17 {
18 // Logic to create order
19 }

99

You might also like