0% found this document useful (0 votes)
3 views704 pages

The C# and .NET Interview Compendium

Uploaded by

daniel.dla
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)
3 views704 pages

The C# and .NET Interview Compendium

Uploaded by

daniel.dla
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/ 704

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 The .NET Version History 1


1.1 .NET Framework 1.0 | C# 1.0 | Visual Studio 2002 | 2002 . . . . . . . . . . . . . . 1
1.2 .NET Framework 1.1 | C# 1.1 | Visual Studio 2003 | 2003 . . . . . . . . . . . . . . 3
1.3 .NET Framework 2.0 | C# 2.0 | Visual Studio 2005 | 2005 . . . . . . . . . . . . . . 4
1.4 .NET Framework 3.0 | C# 2.0 | Visual Studio 2005 | 2006 . . . . . . . . . . . . . . 6
1.5 .NET Framework 3.5 | C# 3.0 | Visual Studio 2008 | 2007 . . . . . . . . . . . . . . 6
1.6 .NET Framework 4.0 | C# 4.0 | Visual Studio 2010 | 2010 . . . . . . . . . . . . . . 9
1.7 .NET Framework 4.5 | C# 5.0 | Visual Studio 2012 | 2012 . . . . . . . . . . . . . . 11
1.8 .NET Framework 4.6 | C# 6.0 | Visual Studio 2015 | 2015 . . . . . . . . . . . . . . 12
1.9 .NET Framework 4.7 | C# 7.0 - 7.3 | Visual Studio 2017 | 2017 . . . . . . . . . . . 13
1.10 .NET Framework 4.8 | C# 7.3 | Visual Studio 2019 | 2019 . . . . . . . . . . . . . . 16
1.11 .NET Core 1.0 | C# 6.0 | Visual Studio 2015 | 2016 . . . . . . . . . . . . . . . . . . 16
1.12 .NET Core 2.0 | C# 7.0 | Visual Studio 2017 | 2017 . . . . . . . . . . . . . . . . . . 16
1.13 .NET Core 3.0 | C# 8.0 | Visual Studio 2019 | 2019 . . . . . . . . . . . . . . . . . . 17
1.14 .NET Core 3.1 (LTS) | C# 8.0 | Visual Studio 2019 | 2019 . . . . . . . . . . . . . . 19
1.15 .NET 5 | C# 9.0 | Visual Studio 2019 | 2020 . . . . . . . . . . . . . . . . . . . . . . 19
1.16 .NET 6 (LTS) | C# 10.0 | Visual Studio 2022 | 2021 . . . . . . . . . . . . . . . . . . 20
1.17 .NET 7 | C# 11.0 | Visual Studio 2022 | 2022 . . . . . . . . . . . . . . . . . . . . . 20
1.18 .NET 8 | C# 12.0 | Visual Studio 2022 | 2023 . . . . . . . . . . . . . . . . . . . . . 23
1.19 .NET 9 | C# 13.0 | Visual Studio 2023 | 2024 . . . . . . . . . . . . . . . . . . . . . 24

2 Data Structures 26
2.1 Arrays in .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.2 Lists in .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.3 Dictionaries in .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.4 Stacks in .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
2.5 Queues in .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
2.6 HashTables and HashSets in .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

ii
2.7 Trees in .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
2.8 Graphs in .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84

3 Algorithms 108
3.1 Sorting Algorithms in .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
3.2 Search Algorithms in .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
3.3 Graph Algorithms in .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
3.4 Dynamic Programming in .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166

4 C# Fundamentals 174
4.1 C# Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
4.2 Delegates and Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
4.3 Value Types, Reference Types, Immutable, Semantics . . . . . . . . . . . . . . . . . 195
4.4 Types and Type Differences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
4.5 Collections and LINQ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215

5 OOP Design and Best Practices 229


5.1 Object-Oriented Programming (OOP) . . . . . . . . . . . . . . . . . . . . . . . . . 229
5.2 SOLID Principles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
5.3 DRY (Don’t Repeat Yourself) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
5.4 Clean Code and Best Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
5.5 Incremental Refactor and Code Update Techniques . . . . . . . . . . . . . . . . . . 260

6 Design Patterns 273


6.1 Creational Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
6.2 Structural Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
6.3 Behavioral Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289

7 Memory Management and Performance 307


7.1 Stack and Heap Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
7.2 Garbage Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
7.3 Performance and Optimization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316

8 Databases, TSQL and ORMs 322


8.1 SQL Server Basics, Schemas, Tables, Views, Jobs, SSRS, SSRI, System Databases . 322
8.2 T-SQL (Transact-SQL), Stored Procedures (SProcs),
Views, Functions, etc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
8.3 ORM Concepts and Entity Framework . . . . . . . . . . . . . . . . . . . . . . . . . 340
8.4 LINQ to Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347
8.5 Advanced Topics in Entity Framework and LINQ . . . . . . . . . . . . . . . . . . . 356

iii
9 Advanced C# Concepts 363
9.1 Advanced C# Language Features . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
9.2 Compilation, Managed vs. Unmanaged Code, Intermediate Language (IL), CLR . . 391
9.3 Reflection and Dynamic Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400
9.4 New Features and Enhancements in .NET . . . . . . . . . . . . . . . . . . . . . . . 409

10 System Desing and Architecture 419


10.1 Low-Level and High-Level System Design . . . . . . . . . . . . . . . . . . . . . . . . 419
10.2 Non-Functional Requirements System Design . . . . . . . . . . . . . . . . . . . . . . 430
10.3 Systems Architecture, Components, Modules, and Layers . . . . . . . . . . . . . . . 440
10.4 Architectural Patterns and Message Queues . . . . . . . . . . . . . . . . . . . . . . 454
10.5 System Architecture: Layered Modular Design with
Data, Service, Core, UI, and Common Code Layers . . . . . . . . . . . . . . . . . . 468

11 Concurrency and Multithreading 479


11.1 Concurrency and Multithreading Basics . . . . . . . . . . . . . . . . . . . . . . . . . 479
11.2 Thread Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 489
11.3 Asynchronous Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 498
11.4 Synchronization Primitives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506
11.5 Advanced Concurrency and Multithreading . . . . . . . . . . . . . . . . . . . . . . . 516

12 Testing and Automation 525


12.1 .NET Unit Testing, Techniques, Patterns, and Best Practices . . . . . . . . . . . . . 525
12.2 Practical Unit Test Examples, Code Coverage, and Tools . . . . . . . . . . . . . . . 539
12.3 Advanced Test Automation and CI/CD . . . . . . . . . . . . . . . . . . . . . . . . . 553

13 Web APIs in .Net 563


13.1 Web API REST Service Design With .NET Core . . . . . . . . . . . . . . . . . . . 563
13.2 ASP.Net WebAPI Routing, Endpoints, Controllers, DI, Model Binding, Validations,
Versioning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 572
13.3 Caching and Performance in REST Services . . . . . . . . . . . . . . . . . . . . . . 584
13.4 Web API REST Principles in .NET Core . . . . . . . . . . . . . . . . . . . . . . . . 593
13.5 Web API Security, JWTokens, JSON Tokens and Authentication . . . . . . . . . . . 603
13.6 ASP.Net Core WebApi Middleware . . . . . . . . . . . . . . . . . . . . . . . . . . . 612

14 Cloud Infrastructure and DevOps 625


14.1 Cloud Computing with Azure and Azure Services . . . . . . . . . . . . . . . . . . . 625
14.2 Docker and Virtual Machines (Azure, GitLab) . . . . . . . . . . . . . . . . . . . . . 637
14.3 Azure Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 646
14.4 CI/CD, Builds, and Pipelines for .NET Apps (Azure, GitLab) . . . . . . . . . . . . 656

iv
15 Security in .NET Applications 671
15.1 General Code Security and Best Practices in .NET . . . . . . . . . . . . . . . . . . 671
15.2 Obfuscation, Data Protection, Authentication, and Authorization . . . . . . . . . . 680
15.3 Network Security, Preventing Common Security Vulnerabilities . . . . . . . . . . . . 689

v
1 | The .NET Version History

Understanding the evolution of the .NET platform is essential for any developer aiming to master
modern C# development. Over the years, .NET has introduced numerous features and improve-
ments that have shaped the way applications are built — from foundational enhancements in
language syntax to powerful runtime capabilities and cross-platform support.

In this chapter, we will explore the history of .NET by listing its major version releases along
with the key features introduced in each one. This overview will help you not only understand how
the platform has evolved, but also recognize which features are available depending on the version
you’re working with — a valuable insight during technical interviews and real-world development.

1.1 .NET Framework 1.0 | C# 1.0 | Visual Studio 2002 | 2002


Basic object-oriented features, delegates, attributes, properties, etc.

Delegate Example
A delegate is a type that represents references to methods with a particular parameter list and
return type. It allows methods to be passed as parameters or assigned to variables.

Listing 1.1: Delegate Example

1 public delegate int Calculate ( int a , int b ) ;


2

3 public class Program


4 {
5 public static int Add ( int a , int b )
6 {
7 return a + b ;
8 }
9

10 public static void Main ()


11 {
12 Calculate calc = Add ;

1
1.1. .NET Framework 1.0 | C# 1.0 | Visual Studio 2002 | 2002

13 int result = calc (10 , 20) ;


14 Console . WriteLine ( result ) ; // Output : 30
15 }
16 }

Attributes Example
Attributes provide a powerful method of associating metadata, or declarative information, with
code such as types, methods, properties, and fields. Attributes can be queried at runtime using
reflection.

Listing 1.2: Attributes Example

1 using System ;
2

3 [ Obsolete ( " Use NewMethod instead " ) ]


4 public class ExampleClass
5 {
6 public void OldMethod ()
7 {
8 Console . WriteLine ( " Old method " ) ;
9 }
10

11 public void NewMethod ()


12 {
13 Console . WriteLine ( " New method " ) ;
14 }
15 }
16

17 public class Program


18 {
19 public static void Main ()
20 {
21 ExampleClass obj = new ExampleClass () ;
22 obj . OldMethod () ; // Warning : ’ ExampleClass . OldMethod () ’ is obsolete
23 obj . NewMethod () ; // Output : New method
24 }
25 }

Properties Example
Properties in C# are members that provide a flexible mechanism to read, write, or compute the
values of private fields. They are used to access the values in a class while ensuring encapsulation.

Listing 1.3: Properties Example

2
1.2. .NET Framework 1.1 | C# 1.1 | Visual Studio 2003 | 2003

1 public class Person


2 {
3 // Backing field
4 private string _name ;
5

6 // Property with get and set accessors


7 public string Name
8 {
9 get { return _name ; }
10 set { _name = value ; }
11 }
12 }
13

14 public class Program


15 {
16 public static void Main ()
17 {
18 Person person = new Person () ;
19 person . Name = " John " ;
20 Console . WriteLine ( person . Name ) ; // Output : John
21 }
22 }

1.2 .NET Framework 1.1 | C# 1.1 | Visual Studio 2003 | 2003


Mobile device support, ODBC, Oracle database integration, asynchronous delegates.

Asynchronous Delegate Example


Asynchronous delegates allow methods to be executed asynchronously without explicitly using
‘async‘/‘await‘. A delegate can invoke methods asynchronously using ‘BeginInvoke‘ and ‘EndIn-
voke‘.

Listing 1.4: Asynchronous Delegate Example

1 using System ;
2

3 public delegate int Calculate ( int a , int b ) ;


4

5 public class Program


6 {
7 public static int Add ( int a , int b )
8 {
9 return a + b ;
10 }

3
1.3. .NET Framework 2.0 | C# 2.0 | Visual Studio 2005 | 2005

11

12 public static void Main ()


13 {
14 Calculate calc = Add ;
15

16 // Begin asynchronous execution


17 IAsyncResult asyncResult = calc . BeginInvoke (10 , 20 , null , null ) ;
18

19 // Do other work while the delegate runs ...


20

21 // End asynchronous execution and get the result


22 int result = calc . EndInvoke ( asyncResult ) ;
23

24 Console . WriteLine ( $ " Result : { result } " ) ; // Output : Result : 30


25 }
26 }

1.3 .NET Framework 2.0 | C# 2.0 | Visual Studio 2005 | 2005


Generics, anonymous methods, nullable types, partial classes.

Generics Example
Generics allow you to define classes, methods, and delegates with placeholders for the type of
objects they store or use, providing type safety without redundancy.
Listing 1.5: Generics Example

1 List < int > numbers = new List < int >() ;
2 numbers . Add (10) ;
3 numbers . Add (20) ;

Generics Class Definition Example


This shows how to define a generic class that can operate on any data type while maintaining type
safety.
Listing 1.6: Generics Class Definition Example

1 public class MyGenericClass <T >


2 {
3 public T GenericMethod ( T value )
4 {
5 return value ;
6 }
7 }

4
1.3. .NET Framework 2.0 | C# 2.0 | Visual Studio 2005 | 2005

Anonymous Methods Example


Anonymous methods allow you to define inline methods without giving them a name. They’re
useful when you need short, temporary methods, typically for delegates.

Listing 1.7: Anonymous Methods Example

1 Action < int > print = delegate ( int x )


2 {
3 Console . WriteLine ( x ) ;
4 };
5 print (10) ;

Nullable Types Example


Nullable types allow value types to represent ‘null‘, indicating the absence of a value. This is useful
for representing optional or undefined values.

Listing 1.8: Nullable Types Example

1 public class Program


2 {
3 public 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 nullableInt = 10;
16 Console . WriteLine ( nullableInt ) ; // Output : 10
17 }
18 }

Partial Classes Example


Partial classes allow the definition of a class to be split across multiple files. This is useful in large
projects or when code generation tools are used.

Listing 1.9: Partial Classes Example

1 // File 1

5
1.4. .NET Framework 3.0 | C# 2.0 | Visual Studio 2005 | 2006

2 public partial class MyClass


3 {
4 public void Method1 ()
5 {
6 Console . WriteLine ( " Method1 " ) ;
7 }
8 }
9

10 // File 2
11 public partial class MyClass
12 {
13 public void Method2 ()
14 {
15 Console . WriteLine ( " Method2 " ) ;
16 }
17 }
18

19 public class Program


20 {
21 public static void Main ()
22 {
23 MyClass obj = new MyClass () ;
24 obj . Method1 () ; // Output : Method1
25 obj . Method2 () ; // Output : Method2
26 }
27 }

1.4 .NET Framework 3.0 | C# 2.0 | Visual Studio 2005 | 2006


WPF, WCF, WF, Windows CardSpace.

1.5 .NET Framework 3.5 | C# 3.0 | Visual Studio 2008 | 2007


LINQ, lambda expressions, anonymous types, extension methods, expression trees.

LINQ Example
Language-Integrated Query (LINQ) allows querying collections like arrays or lists using a query-like
syntax in C#.
Listing 1.10: LINQ Example

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


2 var evenNumbers = numbers . Where ( n = > n % 2 == 0) ;

6
1.5. .NET Framework 3.5 | C# 3.0 | Visual Studio 2008 | 2007

Lambda Expressions Example


Lambda expressions provide a concise way to define anonymous methods using a simple syntax,
often used in LINQ queries.

Listing 1.11: Lambda Expressions Example

1 Func < int , int > square = x = > x * x ;


2 Console . WriteLine ( square (5) ) ; // Output : 25

Anonymous Types Example


Anonymous types allow you to create simple object types without explicitly defining a class.

Listing 1.12: Anonymous Types Example

1 var person = new { Name = " Yohan " , Age = 30 };


2 Console . WriteLine ( $ " Name : { person . Name } , Age : { person . Age } " ) ; // Output : Name :
Yohan , Age : 30

Extension Methods Example


Extension methods allow you to add new methods to existing types without modifying them, useful
for extending functionality.

Listing 1.13: Extension Methods Example

1 public static class StringExtensions


2 {
3 public static int WordCount ( this string str )
4 {
5 return str . Split ( new char [] { ’ ’ , ’. ’ , ’? ’ } ,
6 StringSplitOptions . RemoveEmptyEntries ) . Length ;
7 }
8 }
9

10 public class Program


11 {
12 public static void Main ()
13 {
14 string text = " Hello world ! " ;
15 Console . WriteLine ( text . WordCount () ) ; // Output : 2
16 }
17 }

Extension methods allow you to add new methods to existing types without modifying them, useful
for extending functionality.

7
1.5. .NET Framework 3.5 | C# 3.0 | Visual Studio 2008 | 2007

Listing 1.14: Extension Methods Example

1 public static class StringExtensions


2 {
3 public static int WordCount ( this string str )
4 {
5 return str . Split ( new char [] { ’ ’ , ’. ’ , ’? ’ } ,
6 StringSplitOptions . RemoveEmptyEntries ) . Length ;
7 }
8 }
9

10 public class Program


11 {
12 public static void Main ()
13 {
14 string text = " Hello world ! " ;
15 Console . WriteLine ( text . WordCount () ) ; // Output : 2
16 }
17 }

Expression Trees Example


Expression trees represent code in a tree-like data structure, allowing dynamic creation and ma-
nipulation of code at runtime. These trees are used in LINQ providers to translate queries to
formats like SQL.

Listing 1.15: Expression Trees Example

1 using System ;
2 using System . Linq . Expressions ;
3

4 class Program
5 {
6 static void Main ()
7 {
8 // Expression Tree representing a lambda expression : n = > n * 2
9 Expression < Func < int , int > > multiplyByTwo = n = > n * 2;
10

11 // Compiling the expression tree into a delegate


12 Func < int , int > compiledExpression = multiplyByTwo . Compile () ;
13

14 // Using the compiled expression to invoke the function


15 int result = compiledExpression (5) ; // result = 10
16

17 Console . WriteLine ( $ " Result : { result } " ) ;


18 }
19 }

8
1.6. .NET Framework 4.0 | C# 4.0 | Visual Studio 2010 | 2010

1.6 .NET Framework 4.0 | C# 4.0 | Visual Studio 2010 | 2010


Dynamic binding (dynamic keyword), named and optional parameters, covariance and contravari-
ance.

Dynamic Binding Example


The ‘dynamic‘ keyword allows bypassing compile-time type checking, enabling dynamic method
calls and property access at runtime.

Listing 1.16: Dynamic Binding Example

1 dynamic obj = " Hello " ;


2 Console . WriteLine ( obj . Length ) ;

Named and Optional Parameters Example


Named and optional parameters let you specify arguments by name and omit optional ones, im-
proving readability and flexibility.

Listing 1.17: Named and Optional Parameters Example

1 public void PrintMessage ( string message = " Hello " , int count = 1)
2 {
3 for ( int i = 0; i < count ; i ++)
4 {
5 Console . WriteLine ( message ) ;
6 }
7 }
8 PrintMessage ( count : 3) ;

Covariance Example
Covariance allows a method to return a more derived type than that specified by the delegate or
interface. It enables implicit conversion of a delegate or generic type’s return type to a base type.

Listing 1.18: Covariance Example

1 using System ;
2

3 public class Animal {}


4 public class Dog : Animal {}
5

6 public delegate Animal AnimalDelegate () ;


7

8 public class Program

9
1.6. .NET Framework 4.0 | C# 4.0 | Visual Studio 2010 | 2010

9 {
10 public static Dog GetDog ()
11 {
12 return new Dog () ;
13 }
14

15 public static void Main ()


16 {
17 AnimalDelegate animalDel = GetDog ; // Covariance : Dog to Animal
18 Animal animal = animalDel () ;
19 Console . WriteLine ( animal . GetType () ) ; // Output : Dog
20 }
21 }

Contravariance Example
Contravariance allows a method to accept parameters of a less derived type than specified by the
delegate or interface. It enables implicit conversion of a delegate’s parameter types from a base
type to a derived type.

Listing 1.19: Contravariance Example

1 using System ;
2

3 public class Animal {}


4 public class Dog : Animal {}
5

6 public delegate void AnimalHandler ( Dog dog ) ;


7

8 public class Program


9 {
10 public static void HandleAnimal ( Animal animal )
11 {
12 Console . WriteLine ( " Handling an animal " ) ;
13 }
14

15 public static void Main ()


16 {
17 AnimalHandler handler = HandleAnimal ; // Contravariance : Animal to Dog
18 handler ( new Dog () ) ; // Output : Handling an animal
19 }
20 }

10
1.7. .NET Framework 4.5 | C# 5.0 | Visual Studio 2012 | 2012

1.7 .NET Framework 4.5 | C# 5.0 | Visual Studio 2012 | 2012


Async/await, caller info attributes, Portable Class Libraries (PCLs), enhanced HTML5 support
in ASP.NET.

Async/Await Example
The ‘async‘ and ‘await‘ keywords simplify asynchronous programming by allowing asynchronous
method calls without blocking the main thread.

Listing 1.20: Async/Await Example

1 public async Task < int > FetchDataAsync ()


2 {
3 await Task . Delay (1000) ;
4 return 42;
5 }
6

7 public async Task Main ()


8 {
9 int result = await FetchDataAsync () ;
10 Console . WriteLine ( result ) ; // Output : 42
11 }

Caller Info Attributes Example


Caller info attributes allow you to obtain information about the caller of a method, such as the
caller’s member name, file path, and line number.

Listing 1.21: Caller Info Attributes Example

1 public void Log ( string message ,


2 [ CallerMemberName ] string memberName = " " ,
3 [ CallerFilePath ] string filePath = " " ,
4 [ CallerLineNumber ] int lineNumber = 0)
5 {
6 Console . WriteLine ( $ " { message } called from { memberName } , { filePath } , line : {
lineNumber } " ) ;
7 }
8

9 Log ( " Test " ) ;

11
1.8. .NET Framework 4.6 | C# 6.0 | Visual Studio 2015 | 2015

1.8 .NET Framework 4.6 | C# 6.0 | Visual Studio 2015 | 2015


String interpolation, null-conditional operators (?.), auto-property initializers, expression-bodied
members.

String Interpolation Example


String interpolation allows embedding expressions inside string literals using the ‘$‘ symbol for
more readable and maintainable string formatting.

Listing 1.22: String Interpolation Example

1 string name = " Yohan " ;


2 Console . WriteLine ( $ " Hello , { name }! " ) ;

Null-Conditional Operators Example


The null-conditional operator (‘ ?.‘) allows you to safely access members without risking ‘NullRef-
erenceException‘.

Listing 1.23: Null-Conditional Operators Example

1 string ? message = null ;


2 Console . WriteLine ( message ?. Length ) ;

Auto-Property Initializers Example


Auto-property initializers allow you to directly assign default values to properties when defining
them.

Listing 1.24: Auto-Property Initializers Example

1 public class Person


2 {
3 public string Name { get ; set ; } = " Default Name " ;
4 }

Expression-Bodied Members Example


Expression-bodied members provide a concise syntax for methods and properties that contain only
a single expression.

Listing 1.25: Expression-Bodied Members Example

1 public class Person

12
1.9. .NET Framework 4.7 | C# 7.0 - 7.3 | Visual Studio 2017 | 2017

2 {
3 private string name ;
4 public Person ( string name ) = > this . name = name ;
5 public string GetName () = > name ;
6 }

1.9 .NET Framework 4.7 | C# 7.0 - 7.3 | Visual Studio 2017


| 2017
Tuples, local functions, pattern matching, ref locals/returns, out variables.

Tuples Example
Tuples allow grouping multiple values into a single object without explicitly defining a class or
struct.

Listing 1.26: Using Value Name Tuple Syntax Example

1 ( string name , int age ) person = ( " Yohan " , 30) ;


2 Console . WriteLine ( person . name ) ; // Output : Yohan

Using Value Tuple Syntax Example

Listing 1.27: Using Value Tuple Syntax Example

1 public ( string , int ) GetPersonInfo ()


2 {
3 return ( " Yohan " , 30) ; // Returning a value tuple
4 }

Using Named Value Tuple Syntax Example

Listing 1.28: Using Named Value Tuple Syntax Example

1 public ( string name , int age ) GetPersonInfo ()


2 {
3 return ( " Yohan " , 30) ; // Returning a named value tuple
4 }
5

6 public class Program


7 {
8 public static void Main ()
9 {
10 var person = GetPersonInfo () ; // Accessing the named tuple

13
1.9. .NET Framework 4.7 | C# 7.0 - 7.3 | Visual Studio 2017 | 2017

11 Console . WriteLine ( $ " Name : { person . name } , Age : { person . age } " ) ;
12 }
13 }

Using Tuple<T1, T2> Syntax Example

Listing 1.29: Using Tuple<T1, T2> Syntax Example

1 public Tuple < string , int > GetPersonInfo ()


2 {
3 return Tuple . Create ( " Yohan " , 30) ; // Returning a Tuple
4 }

Local Functions Example


Local functions allow you to define functions inside other functions for better encapsulation and
readability.

Listing 1.30: Local Functions Example

1 void PrintSquare ( int x )


2 {
3 int Square ( int value ) = > value * value ;
4 Console . WriteLine ( Square ( x ) ) ;
5 }
6

7 PrintSquare (5) ; // Output : 25

Pattern Matching Example


Pattern matching enhances conditional logic by allowing you to match the shape and type of data
directly in ‘if‘ or ‘switch‘ statements.

Listing 1.31: Pattern Matching Example

1 object obj = 7;
2

3 if ( obj is int number )


4 {
5 Console . WriteLine ( $ " The number is { number } " ) ;
6 }

Ref Locals/Returns Example


Ref locals and returns allow variables to reference the memory location of another variable, enabling
direct manipulation of the original data rather than a copy.

14
1.9. .NET Framework 4.7 | C# 7.0 - 7.3 | Visual Studio 2017 | 2017

Listing 1.32: Ref Locals/Returns Example

1 using System ;
2

3 class Program
4 {
5 static ref int FindElement ( int [] numbers , int value )
6 {
7 for ( int i = 0; i < numbers . Length ; i ++)
8 {
9 if ( numbers [ i ] == value )
10 {
11 return ref numbers [ i ]; // Return by reference
12 }
13 }
14 throw new Exception ( " Value not found " ) ;
15 }
16

17 static void Main ()


18 {
19 int [] numbers = { 1 , 2 , 3 , 4 };
20 ref int element = ref FindElement ( numbers , 3) ; // Get reference to the
element
21

22 element = 10; // Modify the element directly


23 Console . WriteLine ( string . Join ( " , " , numbers ) ) ; // Output : 1 , 2 , 10 , 4
24 }
25 }

Out Variables Example


Out variables allow a method to return multiple values using ‘out‘ parameters. The ‘out‘ keyword
specifies that a method’s argument will be assigned inside the method.
Listing 1.33: Out Variables Example

1 using System ;
2

3 class Program
4 {
5 static void Calculate ( int a , int b , out int sum , out int product )
6 {
7 sum = a + b ;
8 product = a * b ;
9 }
10

11 static void Main ()


12 {

15
1.10. .NET Framework 4.8 | C# 7.3 | Visual Studio 2019 | 2019

13 Calculate (10 , 20 , out int sum , out int product ) ;


14

15 Console . WriteLine ( $ " Sum : { sum } , Product : { product } " ) ;


16 // Output : Sum : 30 , Product : 200
17 }
18 }

1.10 .NET Framework 4.8 | C# 7.3 | Visual Studio 2019 |


2019
High-DPI support, modern cryptography, async Main, nullable reference types (optional).

Async Main Example


‘async Main‘ allows the ‘Main‘ method to be asynchronous, enabling the use of ‘await‘ in your
program’s entry point.
Listing 1.34: Async Main Example

1 public static async Task Main ( string [] args )


2 {
3 await Task . Delay (1000) ;
4 Console . WriteLine ( " Async Main " ) ;
5 }

Nullable Reference Types Example


Nullable reference types help you avoid null reference errors by distinguishing between nullable
and non-nullable reference types at compile-time.
Listing 1.35: Nullable Reference Types Example

1 string ? maybeNull = null ;


2 Console . WriteLine ( maybeNull ?. Length ) ; // Safely handle null without exception

1.11 .NET Core 1.0 | C# 6.0 | Visual Studio 2015 | 2016


Cross-platform support, CoreCLR, ASP.NET Core, new modular runtime.

1.12 .NET Core 2.0 | C# 7.0 | Visual Studio 2017 | 2017


.NET Standard 2.0 support, async Main, tuples, pattern matching, ref returns, better performance.

16
1.13. .NET Core 3.0 | C# 8.0 | Visual Studio 2019 | 2019

Code Examples
See examples in C# 7.0 (Tuples, Pattern Matching, Async Main).

1.13 .NET Core 3.0 | C# 8.0 | Visual Studio 2019 | 2019


async streams, default interface methods, switch expressions, Windows Forms/WPF on Core.

Async Streams Example


Async streams (‘IAsyncEnumerable‘) allow you to asynchronously yield values over time using
‘await foreach‘ for iteration.

Listing 1.36: Async Streams Example

1 public async IAsyncEnumerable < int > GetNumbersAsync ()


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

10 public async Task Main ()


11 {
12 await foreach ( var number in GetNumbersAsync () )
13 {
14 Console . WriteLine ( number ) ; // Output : 0 , 1 , 2 ( with delay )
15 }
16 }

Default Interface Methods Example


Default interface methods allow you to define method implementations directly inside interfaces.

Listing 1.37: Default Interface Methods Example

1 public interface IGreeter


2 {
3 void Greet ( string message ) ;
4 void GreetDefault () = > Greet ( " Hello from Default Method " ) ;
5 }

17
1.13. .NET Core 3.0 | C# 8.0 | Visual Studio 2019 | 2019

Switch Expression Example


Switch expressions provide a more concise syntax for branching logic in C#. They are an expression
form of the ‘switch‘ statement, returning a value based on pattern matching.

Listing 1.38: Switch Expression Example

1 using System ;
2

3 class Program
4 {
5 static string GetDayType ( DayOfWeek day ) = >
6 day switch
7 {
8 DayOfWeek . Saturday or DayOfWeek . Sunday = > " Weekend " ,
9 DayOfWeek . Monday = > " Start of the Week " ,
10 DayOfWeek . Friday = > " Almost Weekend " ,
11 _ = > " Weekday "
12 };
13

14 static void Main ()


15 {
16 DayOfWeek today = DayOfWeek . Monday ;
17 string dayType = GetDayType ( today ) ;
18

19 Console . WriteLine ( $ " Today is { today } , it ’s a { dayType } " ) ;


20 // Output : Today is Monday , it ’s a Start of the Week
21 }
22 }

Listing 1.39: Switch Expression Example

1 int score = 85;


2 string grade = score switch
3 {
4 >= 90 = > " A " ,
5 >= 80 = > " B " ,
6 >= 70 = > " C " ,
7 >= 60 = > " D " ,
8 _ => "F"
9 };
10 Console . WriteLine ( $ " Grade : { grade } " ) ;

18
1.14. .NET Core 3.1 (LTS) | C# 8.0 | Visual Studio 2019 | 2019

1.14 .NET Core 3.1 (LTS) | C# 8.0 | Visual Studio 2019 |


2019
Long-Term Support (LTS) version, Blazor improvements, async streams.

Code Examples
See examples in C# 8.0 (Nullable Reference Types, Async Streams).

1.15 .NET 5 | C# 9.0 | Visual Studio 2019 | 2020


Unified platform (no more .NET Framework/Core), records, init-only setters, top-level statements,
pattern matching improvements.

Records Example
Records provide a concise way to define immutable data types with built-in value equality.
Listing 1.40: Records Example

1 public record Person ( string Name , int Age ) ;


2

3 Person person = new ( " Yohan " , 30) ;


4 Console . WriteLine ( person . Name ) ; // Output : Yohan

Init-Only Setters Example


Init-only properties allow you to set a property’s value during object initialization but make it
immutable afterward.
Listing 1.41: Init-Only Setters Example

1 public class Person


2 {
3 public string Name { get ; init ; }
4 }
5

6 var person = new Person { Name = " Yohan " };

Top-Level Statements Example


Top-level statements allow you to write C# programs without explicitly defining a ‘Main‘ method,
simplifying simple applications.

19
1.16. .NET 6 (LTS) | C# 10.0 | Visual Studio 2022 | 2021

Listing 1.42: Top-Level Statements Example

1 Console . WriteLine ( " Hello , World ! " ) ; // No Main method required

1.16 .NET 6 (LTS) | C# 10.0 | Visual Studio 2022 | 2021


File-scoped namespaces, global using directives, record structs, improved pattern matching, cross-
platform support (MAUI).

File-scoped Namespaces Example


File-scoped namespaces provide a cleaner syntax for declaring namespaces that apply to the entire
file without needing curly braces.

Listing 1.43: File-scoped Namespaces Example

1 namespace MyApp ;
2 class Program { static void Main () { } }

Global Using Directives Example


Global using directives allow you to define ‘using‘ directives globally for the entire project, simpli-
fying code in large projects.

Listing 1.44: Global Using Directives Example

1 global using System ;


2 global using System . Collections . Generic ;

Record Structs Example


Record structs combine the simplicity of records with the performance of value types.

Listing 1.45: Record Structs Example

1 public readonly record struct Point ( int X , int Y ) ;

1.17 .NET 7 | C# 11.0 | Visual Studio 2022 | 2022


Raw string literals, generic math support, static abstract members in interfaces, list patterns,
performance improvements.

20
1.17. .NET 7 | C# 11.0 | Visual Studio 2022 | 2022

Raw String Literals Example


Raw string literals allow you to define multi-line or escape-sequence-free strings with simple de-
limiters, making complex strings more readable.
Listing 1.46: Raw String Literals Example

1 string multiline = " " "


2 This is a
3 multi - line raw string
4 """;

Generic Math Support Example


Generic math support in C# allows writing generic code that operates on numeric types. With the
introduction of new interfaces in .NET, you can perform arithmetic operations on generic types.
Listing 1.47: Generic Math Support Example

1 using System ;
2 using System . Numerics ;
3

4 class Program
5 {
6 // Generic method with math support , constrained to types supporting addition
7 static T Add <T >( T a , T b ) where T : INumber <T >
8 {
9 return a + b ;
10 }
11

12 static void Main ()


13 {
14 int intResult = Add (10 , 20) ;
15 double doubleResult = Add (10.5 , 20.3) ;
16

17 Console . WriteLine ( $ " Int result : { intResult } " ) ; // Output : Int result :
30
18 Console . WriteLine ( $ " Double result : { doubleResult } " ) ; // Output : Double
result : 30.8
19 }
20 }

Static Abstract Members in Interfaces Example


Static abstract members in interfaces allow defining static methods in interfaces that must be
implemented by the types inheriting the interface. This is useful for creating type-safe generic
math operations.

21
1.17. .NET 7 | C# 11.0 | Visual Studio 2022 | 2022

Listing 1.48: Static Abstract Members in Interfaces Example

1 using System ;
2

3 public interface IShape < TSelf >


4 where TSelf : IShape < TSelf >
5 {
6 // Static abstract method to be implemented by derived types
7 static abstract double CalculateArea ( TSelf shape ) ;
8 }
9

10 public class Circle : IShape < Circle >


11 {
12 public double Radius { get ; set ; }
13

14 // Implement the static abstract member


15 public static double CalculateArea ( Circle circle )
16 {
17 return Math . PI * circle . Radius * circle . Radius ;
18 }
19 }
20

21 public class Program


22 {
23 public static void Main ()
24 {
25 Circle circle = new Circle { Radius = 5 };
26 double area = IShape < Circle >. CalculateArea ( circle ) ;
27

28 Console . WriteLine ( $ " Circle Area : { area } " ) ; // Output : Circle Area :
78.53981633974483
29 }
30 }

List Patterns Example


List patterns allow you to match arrays or lists by their structure, making it easier to pattern-match
sequences.

Listing 1.49: List Patterns Example

1 int [] numbers = { 1 , 2 , 3 };
2

3 if ( numbers is [1 , 2 , 3])
4 {
5 Console . WriteLine ( " Pattern matched ! " ) ;
6 }

22
1.18. .NET 8 | C# 12.0 | Visual Studio 2022 | 2023

1.18 .NET 8 | C# 12.0 | Visual Studio 2022 | 2023


Primary constructors for classes, collection expressions, further performance enhancements, better
AOT support.

Primary Constructors for Classes Example


Primary constructors allow you to declare the constructor directly in the class header, simplifying
the process of defining immutable properties.
Listing 1.50: Primary Constructors for Classes Example

1 public class Person ( string name , int age )


2 {
3 public string Name { get ; } = name ;
4 public int Age { get ; } = age ;
5 }
6

7 var person = new Person ( " John Doe " , 30) ;


8 Console . WriteLine ( $ " { person . Name } , { person . Age } " ) ;

Collection Expressions Example


This feature introduces more concise and flexible ways to work with collections, enabling you to
directly create and manipulate collections using expressions.
Listing 1.51: Collection Expressions Example

1 var numbers = [1 , 2 , 3 , 4 , 5];


2 var doubled = [ for n in numbers select n * 2];
3

4 Console . WriteLine ( string . Join ( " , " , doubled ) ) ; // Output : 2 , 4 , 6 , 8 , 10

Further Performance Enhancements


.NET 8 brings additional performance improvements across various libraries and runtimes. These
optimizations enhance memory usage, CPU efficiency, and general execution speed.
Listing 1.52: Better AOT Compilation Support Example

1 // Sample AOT - optimized method :


2 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
3 public static int AddNumbers ( int a , int b )
4 {
5 return a + b ;
6 }

23
1.19. .NET 9 | C# 13.0 | Visual Studio 2023 | 2024

1.19 .NET 9 | C# 13.0 | Visual Studio 2023 | 2024


Enhanced AOT compilation, advanced pattern matching, new containerization features, further
performance improvements, and increased support for cloud-native applications.

Enhanced AOT Compilation Example


.NET 9 further improves AOT compilation, enabling more aggressive optimizations and support
for additional scenarios, resulting in faster startup times and reduced memory usage.

Listing 1.53: Enhanced AOT Compilation Example

1 // Sample method optimized for AOT :


2 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
3 public static string FormatName ( string firstName , string lastName )
4 {
5 return $ " { firstName } { lastName } " ;
6 }

Advanced Pattern Matching Example


C# 13 introduces advanced pattern matching capabilities, including recursive patterns and better
matching for complex data types.

Listing 1.54: Advanced Pattern Matching Example

1 public static string DescribeShape ( object shape ) = > shape switch


2 {
3 Circle ( var radius ) = > $ " Circle with radius { radius } " ,
4 Rectangle ( var width , var height ) = > $ " Rectangle { width } x { height } " ,
5 _ = > " Unknown shape "
6 };

New Containerization Features Example


.NET 9 adds native support for containerization, with new tools and APIs to optimize .NET
apps for cloud environments. This includes built-in tools for building, deploying, and managing
containers.

Listing 1.55: New Containerization Features Example

1 // Example of using . NET 9 container features to run a service :


2 public static void Main ()
3 {
4 var app = WebApplication . Create () ;
5 app . MapGet ( " / " , () = > " Hello from a containerized . NET 9 app ! " ) ;

24
1.19. .NET 9 | C# 13.0 | Visual Studio 2023 | 2024

6 app . Run () ;
7 }

25
2 | Data Structures

Data structures in C# refer to organized ways of storing and managing data within applications.
They range from simple arrays and lists, to more complex structures like dictionaries and queues.
Each data structure offers a unique way of accessing and manipulating information, making some
more efficient for specific tasks than others.

Using the right data structure can significantly improve performance and clarity. For instance,
a list is ideal for sequential manipulation, while a dictionary is perfect for quick lookups by key.
Understanding these differences ensures that developers can optimize their code, reducing memory
usage and execution time.

It’s crucial to grasp how C# data structures operate and why they perform differently. This
knowledge helps troubleshoot common issues, design efficient algorithms, and communicate ef-
fectively with peers during code reviews or interviews. By selecting the right structure for each
problem, you can write cleaner, faster, and more maintainable code.

2.1 Arrays in .NET


What is an array in .NET, and how is it declared?
An array in .NET is a data structure that stores a fixed-size sequential collection of elements of
the same type. Arrays in .NET are zero-indexed, and they can be declared using square brackets
[].

Listing 2.1: Code example

1 // Example : Declaring and initializing an array


2

3 int [] numbers = new int [5]; // Declaration with a fixed size of 5


4 numbers [0] = 10;
5 numbers [1] = 20;
6 numbers [2] = 30;
7 numbers [3] = 40;

26
2.1. Arrays in .NET

8 numbers [4] = 50;


9

10 // Alternatively , initialize an array with values


11 int [] numbersInitialized = { 10 , 20 , 30 , 40 , 50 };

What is the difference between one-dimensional and multi-dimensional


array in .NET?
A one-dimensional array is a simple linear structure where elements are accessed by a single index.
A multi-dimensional array (such as a 2D or 3D array) requires multiple indices to access elements
and represents more complex structures like matrices or grids.

Listing 2.2: Code example

1 // Example : Declaring and initializing a two - dimensional array


2

3 int [ ,] matrix = new int [3 , 2] {


4 { 1, 2 },
5 { 3, 4 },
6 { 5, 6 }
7 };
8

9 int value = matrix [1 , 1]; // Accessing the element in the second row and second
column ( value is 4)

What are jagged arrays in .NET, and how do they differ from multi-
dimensional arrays?
A jagged array in .NET is an array of arrays, where each element can hold a different-sized array.
Unlike multi-dimensional arrays, where each dimension must have the same length, jagged arrays
allow for varying lengths for each array within the structure.

Listing 2.3: Code example

1 // Example : Declaring and initializing a jagged array


2

3 int [][] jaggedArray = new int [3][];


4 jaggedArray [0] = new int [] { 1 , 2 , 3 };
5 jaggedArray [1] = new int [] { 4 , 5 };
6 jaggedArray [2] = new int [] { 6 , 7 , 8 , 9 };
7

8 int value = jaggedArray [1][1]; // Accessing the second element of the second array
( value is 5)

27
2.1. Arrays in .NET

How do you sort an array in .NET?


You can sort an array in .NET using the Array.Sort() method, which sorts the elements in
ascending order by default. You can also pass a custom comparison delegate if you need a custom
sorting order.

Listing 2.4: Code example

1 // Example : Sorting an array in ascending order


2

3 int [] numbers = { 5 , 3 , 8 , 1 , 2 };
4 Array . Sort ( numbers ) ; // Sorting the array
5

6 // Result : numbers = { 1 , 2 , 3 , 5 , 8 }

How do you reverse an array in .NET?


You can reverse an array in .NET using the Array.Reverse() method, which reverses the order
of the elements in the array.

Listing 2.5: Code example

1 // Example : Reversing an array


2

3 int [] numbers = { 1 , 2 , 3 , 4 , 5 };
4 Array . Reverse ( numbers ) ; // Reversing the array
5

6 // Result : numbers = { 5 , 4 , 3 , 2 , 1 }

How do you copy one array to another in .NET?


You can copy elements from one array to another using the Array.Copy() method, which allows
you to specify the source array, destination array, and number of elements to copy.

Listing 2.6: Code example

1 // Example : Copying an array


2

3 int [] source = { 1 , 2 , 3 , 4 , 5 };
4 int [] destination = new int [5];
5 Array . Copy ( source , destination , source . Length ) ;
6

7 // Now destination contains the same elements as source

28
2.1. Arrays in .NET

How do you find the length of an array in .NET?


You can find the length of an array in .NET using the Length property, which returns the total
number of elements in the array.

Listing 2.7: Code example

1 // Example : Getting the length of an array


2

3 int [] numbers = { 1 , 2 , 3 , 4 , 5 };
4 int length = numbers . Length ; // Length is 5

How do you resize an array in .NET?


You can resize an array using the Array.Resize() method. This method creates a new array with
the specified size and copies the elements of the original array to the new array. If the new size is
smaller, elements are truncated.

Listing 2.8: Code example

1 // Example : Resizing an array


2

3 int [] numbers = { 1 , 2 , 3 , 4 , 5 };
4 Array . Resize ( ref numbers , 7) ; // Resize the array to have 7 elements
5

6 // Result : numbers = { 1 , 2 , 3 , 4 , 5 , 0 , 0 }

How do you clear an array in .NET?


You can clear an array using the Array.Clear() method, which sets the specified range of elements
in the array to their default value (e.g., null for reference types and 0 for numeric types).

Listing 2.9: Code example

1 // Example : Clearing an array


2

3 int [] numbers = { 1 , 2 , 3 , 4 , 5 };
4 Array . Clear ( numbers , 0 , numbers . Length ) ; // Clears the entire array
5

6 // Result : numbers = { 0 , 0 , 0 , 0 , 0 }

How do you find an element in an array in .NET?


You can find an element in an array using the Array.IndexOf() method, which returns the index
of the first occurrence of the element, or -1 if the element is not found.

29
2.1. Arrays in .NET

Listing 2.10: Code example

1 // Example : Finding an element in an array


2

3 int [] numbers = { 1 , 2 , 3 , 4 , 5 };
4 int index = Array . IndexOf ( numbers , 3) ; // Index is 2
5

6 int notFound = Array . IndexOf ( numbers , 10) ; // Result is -1 because 10 is not in


the array

How do you use foreach to iterate over an array in .NET?


The foreach loop in C# is used to iterate over each element in an array. It is useful for reading
values, but it does not allow you to modify elements directly.

Listing 2.11: Code example

1 // Example : Using foreach to iterate over an array


2

3 int [] numbers = { 1 , 2 , 3 , 4 , 5 };
4

5 foreach ( int number in numbers )


6 {
7 Console . WriteLine ( number ) ; // Outputs each element in the array
8 }

How do you initialize an array of objects in .NET?


You can initialize an array of objects in .NET by specifying the object type and creating new
instances for each element in the array.

Listing 2.12: Code example

1 // Example : Initializing an array of objects


2

3 Person [] people = new Person [2];


4 people [0] = new Person { Name = " Alice " };
5 people [1] = new Person { Name = " Bob " };
6

7 @code {
8 public class Person {
9 public string Name { get ; set ; }
10 }
11 }

30
2.1. Arrays in .NET

How do you pass an array to a method in .NET?


In .NET, you can pass an array to a method just like any other parameter. Arrays are passed by
reference, meaning changes made to the array inside the method will affect the original array.

Listing 2.13: Code example

1 // Example : Passing an array to a method


2

3 public void ProcessArray ( int [] numbers )


4 {
5 for ( int i = 0; i < numbers . Length ; i ++)
6 {
7 numbers [ i ] *= 2; // Modify each element
8 }
9 }
10

11 int [] myNumbers = { 1 , 2 , 3 , 4 , 5 };
12 ProcessArray ( myNumbers ) ; // Modifies the original array

How do you return an array from a method in .NET?


You can return an array from a method in .NET by specifying the return type as an array in the
method signature and returning the array.

Listing 2.14: Code example

1 // Example : Returning an array from a method


2

3 public int [] CreateArray ( int size )


4 {
5 int [] newArray = new int [ size ];
6 for ( int i = 0; i < size ; i ++)
7 {
8 newArray [ i ] = i * 10;
9 }
10 return newArray ;
11 }
12

13 int [] result = CreateArray (5) ; // Returns { 0 , 10 , 20 , 30 , 40 }

What is the difference between the Array class and the List class in .NET?
The Array class is a fixed-size collection, whereas the List<T> class is a dynamic collection that
can grow or shrink as needed. List<T> provides additional methods for adding, removing, and
manipulating elements.

31
2.2. Lists in .NET

Listing 2.15: Code example

1 // Example : Array vs . List


2

3 // Fixed - size array


4 int [] array = new int [5];
5

6 // Dynamic List
7 List < int > list = new List < int >() ;
8 list . Add (1) ;
9 list . Add (2) ;
10 list . Add (3) ;

How does the Array.Find method work in .NET?


The Array.Find method searches for the first element in an array that matches a condition defined
by a predicate. It returns the element if found, or the default value if no match is found.

Listing 2.16: Code example

1 // Example : Using Array . Find to find an element


2

3 int [] numbers = { 1 , 2 , 3 , 4 , 5 };
4 int found = Array . Find ( numbers , n = > n > 3) ; // Returns 4

How do you create a shallow copy of an array in .NET?


A shallow copy of an array can be created using the Clone() method, which creates a copy of the
array’s references, but not the objects they point to (for reference types).

Listing 2.17: Code example

1 // Example : Creating a shallow copy of an array


2

3 int [] original = { 1 , 2 , 3 };
4 int [] shallowCopy = ( int []) original . Clone () ; // Shallow copy of the array

2.2 Lists in .NET


What is a List<T> in .NET, and how does it differ from an array?
A List<T> in .NET is a generic collection that allows dynamic resizing. Unlike arrays, which have
a fixed size, List<T> can grow or shrink as elements are added or removed. Additionally, List<T>
offers methods for inserting, removing, and searching for elements, providing more flexibility com-
pared to arrays.

32
2.2. Lists in .NET

Listing 2.18: Code example

1 // Example : Declaring and initializing a List <T >


2

3 List < int > numbers = new List < int > { 1 , 2 , 3 , 4 , 5 };
4 numbers . Add (6) ; // List dynamically resizes to accommodate the new element

How does the Add() method work in List<T>?


The Add() method appends an element to the end of the List<T>. If the list has reached its
capacity, it automatically increases its capacity by allocating more memory to store new elements.

Listing 2.19: Code example

1 // Example : Adding elements to a List <T >


2

3 List < string > names = new List < string >() ;
4 names . Add ( " Alice " ) ;
5 names . Add ( " Bob " ) ;

How do you insert an element at a specific position in a List<T>?


The Insert() method allows you to insert an element at a specified index. The list automatically
shifts the elements to accommodate the new element.

Listing 2.20: Code example

1 // Example : Inserting an element in a List <T >


2

3 List < int > numbers = new List < int > { 1 , 2 , 4 , 5 };
4 numbers . Insert (2 , 3) ; // Inserts 3 at index 2 , shifting the subsequent elements

How do you remove an element from a List<T> by value?


The Remove() method removes the first occurrence of the specified element from the list. If the
element is not found, the list remains unchanged.

Listing 2.21: Code example

1 // Example : Removing an element from a List <T >


2

3 List < int > numbers = new List < int > { 1 , 2 , 3 , 4 , 5 };
4 numbers . Remove (3) ; // Removes the element 3 from the list

33
2.2. Lists in .NET

How do you remove an element from a List<T> by index?


The RemoveAt() method removes an element from the list at the specified index. The list auto-
matically shifts the elements to fill the gap created by the removed element.

Listing 2.22: Code example

1 // Example : Removing an element by index


2

3 List < string > names = new List < string > { " Alice " , " Bob " , " Charlie " };
4 names . RemoveAt (1) ; // Removes " Bob " at index 1

How do you check if a List<T> contains a specific element?


The Contains() method checks whether the list contains the specified element. It returns true if
the element is found; otherwise, it returns false.

Listing 2.23: Code example

1 // Example : Checking if a List <T > contains a specific element


2

3 List < int > numbers = new List < int > { 1 , 2 , 3 , 4 , 5 };
4 bool hasThree = numbers . Contains (3) ; // Returns true

How do you find the index of an element in a List<T>?


The IndexOf() method returns the index of the first occurrence of the specified element in the
list. If the element is not found, it returns -1.

Listing 2.24: Code example

1 // Example : Finding the index of an element in a List <T >


2

3 List < string > names = new List < string > { " Alice " , " Bob " , " Charlie " };
4 int index = names . IndexOf ( " Bob " ) ; // Returns 1

How do you iterate over the elements of a List<T> using a foreach loop?
You can use a foreach loop to iterate over the elements in a List<T>. The loop automatically
retrieves each element in the order in which they appear in the list.

Listing 2.25: Code example

1 // Example : Iterating over a List <T > using foreach


2

3 List < int > numbers = new List < int > { 1 , 2 , 3 , 4 , 5 };
4 foreach ( int number in numbers )

34
2.2. Lists in .NET

5 {
6 Console . WriteLine ( number ) ;
7 }

How do you sort a List<T> in ascending order?


The Sort() method sorts the elements of the list in ascending order using the default comparer
for the type T. You can also pass a custom comparison delegate if you need custom sorting logic.

Listing 2.26: Code example

1 // Example : Sorting a List <T > in ascending order


2

3 List < int > numbers = new List < int > { 5 , 2 , 9 , 1 , 3 };
4 numbers . Sort () ; // Sorts the list in ascending order

How do you reverse the order of elements in a List<T>?


The Reverse() method reverses the order of elements in the list, in place, meaning no new list is
created.

Listing 2.27: Code example

1 // Example : Reversing a List <T >


2

3 List < int > numbers = new List < int > { 1 , 2 , 3 , 4 , 5 };
4 numbers . Reverse () ; // Reverses the order of elements

How do you find the count of elements in a List<T>?


The Count property returns the number of elements currently in the List<T>.

Listing 2.28: Code example

1 // Example : Getting the count of elements in a List <T >


2

3 List < string > names = new List < string > { " Alice " , " Bob " , " Charlie " };
4 int count = names . Count ; // Returns 3

How do you clear all elements from a List<T>?


The Clear() method removes all elements from the list, leaving it empty.

Listing 2.29: Code example

1 // Example : Clearing a List <T >

35
2.2. Lists in .NET

3 List < int > numbers = new List < int > { 1 , 2 , 3 , 4 , 5 };
4 numbers . Clear () ; // Removes all elements , the list is now empty

How do you convert an array to a List<T>?


You can convert an array to a List<T> using the ToList() method from System.Linq or by passing
the array to the List<T> constructor.

Listing 2.30: Code example

1 // Example : Converting an array to a List <T >


2

3 int [] numbersArray = { 1 , 2 , 3 , 4 , 5 };
4 List < int > numbersList = numbersArray . ToList () ;

How do you convert a List<T> to an array?


You can convert a List<T> to an array using the ToArray() method. This creates a new array
with the same elements as the list.

Listing 2.31: Code example

1 // Example : Converting a List <T > to an array


2

3 List < int > numbers = new List < int > { 1 , 2 , 3 , 4 , 5 };
4 int [] numbersArray = numbers . ToArray () ;

How do you remove all elements that match a specific condition in a


List<T>?
The RemoveAll() method removes all elements from the list that satisfy the specified predicate (a
condition in the form of a lambda expression).

Listing 2.32: Code example

1 // Example : Removing elements based on a condition


2

3 List < int > numbers = new List < int > { 1 , 2 , 3 , 4 , 5 , 6 };
4 numbers . RemoveAll ( n = > n % 2 == 0) ; // Removes all even numbers

How do you find an element in a List<T> based on a condition?


The Find() method returns the first element in the list that matches a specified condition (predi-
cate). If no element is found, it returns the default value for the type T.

36
2.2. Lists in .NET

Listing 2.33: Code example

1 // Example : Finding an element based on a condition


2

3 List < int > numbers = new List < int > { 1 , 2 , 3 , 4 , 5 };
4 int foundNumber = numbers . Find ( n = > n > 3) ; // Returns 4

How do you get a sublist from an existing List<T>?


The GetRange() method returns a sublist that is a copy of a specified range of elements from the
original list.

Listing 2.34: Code example

1 // Example : Getting a sublist from a List <T >


2

3 List < int > numbers = new List < int > { 1 , 2 , 3 , 4 , 5 };
4 List < int > subList = numbers . GetRange (1 , 3) ; // Returns {2 , 3 , 4}

How do you ensure thread-safe access to a List<T> in a multithreaded


environment?
To ensure thread-safe access, you can either use locks (via the lock statement) or use a thread-safe
collection like ConcurrentBag<T> from System.Collections.Concurrent instead of List<T>.

Listing 2.35: Code example

1 // Example : Thread - safe access to a List <T > using locks


2

3 private static List < int > numbers = new List < int >() ;
4 private static object lockObject = new object () ;
5

6 public void AddNumber ( int number )


7 {
8 lock ( lockObject )
9 {
10 numbers . Add ( number ) ;
11 }
12 }

How do you filter elements from a List<T> using LINQ?


You can filter elements in a List<T> using the LINQ Where() method, which returns a new list of
elements that match the specified condition.

37
2.3. Dictionaries in .NET

Listing 2.36: Code example

1 // Example : Filtering a List <T > using LINQ


2

3 List < int > numbers = new List < int > { 1 , 2 , 3 , 4 , 5 , 6 };
4 List < int > evenNumbers = numbers . Where ( n = > n % 2 == 0) . ToList () ; // Returns {2 , 4 ,
6}

How do you use ForEach() to iterate through a List<T> in .NET?


The ForEach() method allows you to iterate through each element in the list and apply a specified
action (such as a lambda expression) to each element.

Listing 2.37: Code example

1 // Example : Using ForEach () on a List <T >


2

3 List < int > numbers = new List < int > { 1 , 2 , 3 , 4 , 5 };
4 numbers . ForEach ( n = > Console . WriteLine ( n ) ) ; // Outputs each element

2.3 Dictionaries in .NET


What is a Dictionary<TKey, TValue> in .NET and how is it different from
other collections?
A Dictionary<TKey, TValue> is a generic collection that stores key-value pairs. Each key must
be unique, and the values can be retrieved by using the corresponding key. Unlike lists, which
store items by index, dictionaries use keys for fast lookups.

Listing 2.38: Code example

1 // Example : Declaring and initializing a Dictionary


2

3 Dictionary < int , string > dictionary = new Dictionary < int , string >() ;
4 dictionary . Add (1 , " Alice " ) ;
5 dictionary . Add (2 , " Bob " ) ;
6

7 // Access value by key


8 string name = dictionary [1]; // " Alice "

How do you add key-value pairs to a dictionary?


You can add key-value pairs to a Dictionary<TKey, TValue> using the Add() method. Each key
must be unique, and attempting to add a duplicate key will throw an exception.

38
2.3. Dictionaries in .NET

Listing 2.39: Code example

1 // Example : Adding key - value pairs to a Dictionary


2

3 Dictionary < string , int > ageDictionary = new Dictionary < string , int >() ;
4 ageDictionary . Add ( " Alice " , 25) ;
5 ageDictionary . Add ( " Bob " , 30) ;

How do you check if a key exists in a dictionary in .NET?


The ContainsKey() method checks if a specific key exists in the dictionary. It returns true if the
key is present, and false otherwise.

Listing 2.40: Code example

1 // Example : Checking if a key exists


2

3 Dictionary < int , string > dictionary = new Dictionary < int , string >
4 {
5 { 1 , " Alice " } ,
6 { 2 , " Bob " }
7 };
8

9 bool exists = dictionary . ContainsKey (1) ; // Returns true

How do you remove a key-value pair from a dictionary in .NET?


You can remove a key-value pair using the Remove() method by specifying the key. If the key
exists, the key-value pair is removed; otherwise, the dictionary remains unchanged.

Listing 2.41: Code example

1 // Example : Removing a key - value pair


2

3 Dictionary < string , int > ageDictionary = new Dictionary < string , int >
4 {
5 { " Alice " , 25 } ,
6 { " Bob " , 30 }
7 };
8

9 ageDictionary . Remove ( " Alice " ) ; // Removes the key " Alice "

How do you retrieve a value from a dictionary using a key in .NET?


You can retrieve a value from a dictionary by using the indexer ([]) with the key. If the key is
not found, an exception will be thrown.

39
2.3. Dictionaries in .NET

Listing 2.42: Code example

1 // Example : Retrieving a value from a Dictionary


2

3 Dictionary < int , string > dictionary = new Dictionary < int , string >
4 {
5 { 1 , " Alice " } ,
6 { 2 , " Bob " }
7 };
8

9 string value = dictionary [1]; // Retrieves " Alice "

How do you safely retrieve a value from a dictionary without throwing


an exception if the key doesn’t exist?
The TryGetValue() method allows you to safely retrieve a value from the dictionary. It returns
true if the key is found, along with the value, or false if the key doesn’t exist.

Listing 2.43: Code example

1 // Example : Using TryGetValue to safely retrieve a value


2

3 Dictionary < int , string > dictionary = new Dictionary < int , string >
4 {
5 { 1 , " Alice " } ,
6 { 2 , " Bob " }
7 };
8

9 if ( dictionary . TryGetValue (1 , out string value ) )


10 {
11 Console . WriteLine ( value ) ; // " Alice "
12 }
13 else
14 {
15 Console . WriteLine ( " Key not found " ) ;
16 }

How do you iterate through all key-value pairs in a dictionary in .NET?


You can iterate through a dictionary using a foreach loop, where each iteration provides a
KeyValuePair<TKey, TValue> that contains both the key and value.

Listing 2.44: Code example

1 // Example : Iterating through a Dictionary


2

3 Dictionary < string , int > ageDictionary = new Dictionary < string , int >

40
2.3. Dictionaries in .NET

4 {
5 { " Alice " , 25 } ,
6 { " Bob " , 30 }
7 };
8

9 foreach ( KeyValuePair < string , int > entry in ageDictionary )


10 {
11 Console . WriteLine ( $ " { entry . Key } is { entry . Value } years old . " ) ;
12 }

How do you clear all elements from a dictionary in .NET?


You can clear all key-value pairs in a dictionary using the Clear() method, leaving the dictionary
empty.

Listing 2.45: Code example

1 // Example : Clearing a Dictionary


2

3 Dictionary < int , string > dictionary = new Dictionary < int , string >
4 {
5 { 1 , " Alice " } ,
6 { 2 , " Bob " }
7 };
8

9 dictionary . Clear () ; // Removes all key - value pairs

How do you check how many key-value pairs are in a dictionary in .NET?
You can check the number of key-value pairs in a dictionary by accessing the Count property.

Listing 2.46: Code example

1 // Example : Checking the count of elements in a Dictionary


2

3 Dictionary < int , string > dictionary = new Dictionary < int , string >
4 {
5 { 1 , " Alice " } ,
6 { 2 , " Bob " }
7 };
8

9 int count = dictionary . Count ; // Returns 2

41
2.3. Dictionaries in .NET

Can you use a custom type as a key in a dictionary in .NET?


Yes, you can use a custom type as a key in a dictionary, but the custom type must implement the
GetHashCode() and Equals() methods to ensure that keys are compared and hashed correctly.

Listing 2.47: Code example

1 // Example : Using a custom type as a key in a Dictionary


2

3 class Person
4 {
5 public string FirstName { get ; set ; }
6 public string LastName { get ; set ; }
7

8 public override bool Equals ( object obj )


9 {
10 return obj is Person person &&
11 FirstName == person . FirstName &&
12 LastName == person . LastName ;
13 }
14

15 public override int GetHashCode ()


16 {
17 return HashCode . Combine ( FirstName , LastName ) ;
18 }
19 }
20

21 Dictionary < Person , int > personAgeDictionary = new Dictionary < Person , int >() ;
22 personAgeDictionary . Add ( new Person { FirstName = " Alice " , LastName = " Smith " } ,
30) ;

How do you merge two dictionaries in .NET?


You can merge two dictionaries by iterating through the second dictionary and adding each key-
value pair to the first dictionary. If a key already exists, you can update the value or handle it as
needed.

Listing 2.48: Code example

1 // Example : Merging two dictionaries


2

3 Dictionary < string , int > dict1 = new Dictionary < string , int >
4 {
5 { " Alice " , 25 } ,
6 { " Bob " , 30 }
7 };
8

42
2.3. Dictionaries in .NET

9 Dictionary < string , int > dict2 = new Dictionary < string , int >
10 {
11 { " Charlie " , 35 } ,
12 { " Alice " , 28 } // Key conflict
13 };
14

15 foreach ( var kvp in dict2 )


16 {
17 dict1 [ kvp . Key ] = kvp . Value ; // Updates or adds the value
18 }

How do you check if a dictionary contains a specific value in .NET?


The ContainsValue() method checks if a dictionary contains a specific value. It returns true if
the value is found, false otherwise.

Listing 2.49: Code example

1 // Example : Checking if a Dictionary contains a value


2

3 Dictionary < string , int > ageDictionary = new Dictionary < string , int >
4 {
5 { " Alice " , 25 } ,
6 { " Bob " , 30 }
7 };
8

9 bool containsValue = ageDictionary . ContainsValue (30) ; // Returns true

How do you update a value for a specific key in a dictionary in .NET?


You can update the value for a specific key by using the indexer ([]) and assigning the new value.

Listing 2.50: Code example

1 // Example : Updating a value in a Dictionary


2

3 Dictionary < int , string > dictionary = new Dictionary < int , string >
4 {
5 { 1 , " Alice " } ,
6 { 2 , " Bob " }
7 };
8

9 dictionary [1] = " Alice Smith " ; // Updates the value for key 1

43
2.3. Dictionaries in .NET

How do you copy a dictionary to another dictionary in .NET?


You can copy one dictionary to another by using the Add() method or by initializing the new
dictionary with the old dictionary using the constructor.

Listing 2.51: Code example

1 // Example : Copying a Dictionary


2

3 Dictionary < string , int > original = new Dictionary < string , int >
4 {
5 { " Alice " , 25 } ,
6 { " Bob " , 30 }
7 };
8

9 Dictionary < string , int > copy = new Dictionary < string , int >( original ) ; // Copies
the dictionary

How do you prevent duplicate keys when adding to a dictionary in .NET?


You can prevent duplicate keys by checking if the key exists using ContainsKey() before adding
the key-value pair.

Listing 2.52: Code example

1 // Example : Preventing duplicate keys in a Dictionary


2

3 Dictionary < string , int > ageDictionary = new Dictionary < string , int >
4 {
5 { " Alice " , 25 }
6 };
7

8 if (! ageDictionary . ContainsKey ( " Alice " ) )


9 {
10 ageDictionary . Add ( " Alice " , 30) ; // This line won ’t execute because " Alice "
already exists
11 }

How do you work with read-only dictionaries in .NET?


You can create a read-only dictionary by wrapping an existing dictionary using the
ReadOnlyDictionary<TKey, TValue> class, which ensures that no modifications can be made to
the dictionary.

Listing 2.53: Code example

1 // Example : Creating a read - only dictionary

44
2.3. Dictionaries in .NET

3 Dictionary < string , int > ageDictionary = new Dictionary < string , int >
4 {
5 { " Alice " , 25 } ,
6 { " Bob " , 30 }
7 };
8

9 var readOnlyDict = new ReadOnlyDictionary < string , int >( ageDictionary ) ;


10 // readOnlyDict . Add (" Charlie " , 35) ; // This line will throw an exception

How do you ensure thread-safe access to a dictionary in a multi-threaded


environment?
By Using ConcurrentDictionary<TKey, TValue> class from System.Collections.Concurrent,
which is designed for thread-safe access in multi-threaded environments.

Listing 2.54: Code example

1 // Example : Using ConcurrentDictionary for thread - safe access


2

3 ConcurrentDictionary < string , int > concurrentDict = new ConcurrentDictionary < string
, int >() ;
4 concurrentDict . TryAdd ( " Alice " , 25) ;
5 concurrentDict . TryUpdate ( " Alice " , 30 , 25) ; // Thread - safe update

How do you convert a Dictionary<TKey, TValue> to a list of


KeyValuePair<TKey, TValue>?
You can convert a dictionary to a list of KeyValuePair<TKey, TValue> using the ToList() method
from LINQ.

Listing 2.55: Code example

1 // Example : Converting a Dictionary to a list of KeyValuePair


2

3 Dictionary < string , int > ageDictionary = new Dictionary < string , int >
4 {
5 { " Alice " , 25 } ,
6 { " Bob " , 30 }
7 };
8

9 List < KeyValuePair < string , int > > keyValueList = ageDictionary . ToList () ;

45
2.4. Stacks in .NET

2.4 Stacks in .NET


What is a Stack in .NET and how does it work?
A Stack<T> is a last-in, first-out (LIFO) collection in .NET, meaning that the last element added
is the first to be removed. It supports common stack operations like Push(), Pop(), and Peek().
Listing 2.56: Code example

1 // Example : Creating a stack and performing operations


2

3 Stack < int > stack = new Stack < int >() ;
4 stack . Push (1) ; // Adds 1 to the stack
5 stack . Push (2) ; // Adds 2 to the stack
6

7 int topElement = stack . Pop () ; // Removes and returns the top element (2)
8 int nextElement = stack . Peek () ; // Returns the top element (1) without removing it

How does the Push() method work in a stack?


The Push() method adds an element to the top of the stack, making it the most recent element
to be popped or peeked at.
Listing 2.57: Code example

1 // Example : Using Push to add elements to a stack


2

3 Stack < string > stack = new Stack < string >() ;
4 stack . Push ( " first " ) ;
5 stack . Push ( " second " ) ;
6

7 Console . WriteLine ( stack . Peek () ) ; // Outputs : second

How do you remove the top element from a stack in .NET?


The Pop() method removes and returns the top element from the stack. If the stack is empty, an
InvalidOperationException is thrown.
Listing 2.58: Code example

1 // Example : Removing the top element using Pop


2

3 Stack < int > stack = new Stack < int >() ;
4 stack . Push (1) ;
5 stack . Push (2) ;
6

7 int removedElement = stack . Pop () ; // Removes and returns 2

46
2.4. Stacks in .NET

What is the Peek() method in a stack and how does it differ from Pop()?
The Peek() method returns the top element of the stack without removing it, whereas Pop()
removes the top element.

Listing 2.59: Code example

1 // Example : Using Peek to view the top element


2

3 Stack < string > stack = new Stack < string >() ;
4 stack . Push ( " first " ) ;
5 stack . Push ( " second " ) ;
6

7 string topElement = stack . Peek () ; // Returns " second " without removing it

How do you check if a stack is empty in .NET?


The Count property can be used to check if a stack is empty. Alternatively, you can use the Any()
method from LINQ or check for stack.Count == 0.

How do you clear all elements from a stack in .NET?


The Clear() method removes all elements from the stack, leaving it empty.

Listing 2.60: Code example

1 // Example : Clearing a stack


2

3 Stack < string > stack = new Stack < string >() ;
4 stack . Push ( " first " ) ;
5 stack . Push ( " second " ) ;
6

7 stack . Clear () ; // Removes all elements , stack is now empty

How do you iterate over a stack in .NET?


You can iterate over a stack using a foreach loop. The iteration happens in LIFO order, meaning
the top element is iterated first.

Listing 2.61: Code example

1 // Example : Iterating over a stack


2

3 Stack < int > stack = new Stack < int >() ;
4 stack . Push (1) ;
5 stack . Push (2) ;
6 stack . Push (3) ;

47
2.4. Stacks in .NET

8 foreach ( int item in stack )


9 {
10 Console . WriteLine ( item ) ; // Outputs 3 , 2 , 1
11 }

How do you convert a stack to an array in .NET?


You can convert a stack to an array using the ToArray() method. The array will be in LIFO order,
meaning the top element of the stack will be the first element in the array.

Listing 2.62: Code example

1 // Example : Converting a stack to an array


2

3 Stack < int > stack = new Stack < int >() ;
4 stack . Push (1) ;
5 stack . Push (2) ;
6

7 int [] array = stack . ToArray () ; // Returns [2 , 1]

How do you check if a stack contains a specific element in .NET?


The Contains() method checks whether a specific element exists in the stack. It returns true if
the element is found, false otherwise.

Listing 2.63: Code example

1 // Example : Checking if a stack contains a specific element


2

3 Stack < string > stack = new Stack < string >() ;
4 stack . Push ( " first " ) ;
5 stack . Push ( " second " ) ;
6

7 bool containsElement = stack . Contains ( " first " ) ; // Returns true

What is the time complexity of Push(), Pop(), Peek() in a stack?


The time complexity of Push(), Pop(), and Peek() operations in a stack is O(1) since each oper-
ation involves adding, removing, or retrieving the top element only.

Listing 2.64: Code example

1 // Example : Time complexity remains constant for basic stack operations


2

3 Stack < int > stack = new Stack < int >() ;

48
2.4. Stacks in .NET

4 stack . Push (10) ; // O (1)


5 int top = stack . Peek () ; // O (1)
6 int removed = stack . Pop () ; // O (1)

How do you reverse a stack in .NET?


You can reverse a stack by popping its elements into a temporary list or array and then pushing
them back onto the stack.

Listing 2.65: Code example

1 // Example : Reversing a stack


2

3 Stack < int > stack = new Stack < int >() ;
4 stack . Push (1) ;
5 stack . Push (2) ;
6 stack . Push (3) ;
7

8 int [] array = stack . ToArray () ;


9 Array . Reverse ( array ) ;
10 stack . Clear () ;
11

12 foreach ( int item in array )


13 {
14 stack . Push ( item ) ; // Stack is now reversed
15 }

Can you use a stack with custom types in .NET?


Yes, you can use a Stack<T> with custom types. Ensure that your custom type properly implements
equality if you plan to use operations like Contains().

Listing 2.66: Code example

1 // Example : Using a Stack with custom types


2

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

8 Stack < Person > personStack = new Stack < Person >() ;
9 personStack . Push ( new Person { Name = " Alice " }) ;
10 personStack . Push ( new Person { Name = " Bob " }) ;
11

12 Person topPerson = personStack . Pop () ; // " Bob "

49
2.4. Stacks in .NET

How do you copy one stack to another in .NET?


You can copy a stack to another stack by iterating over it and pushing elements onto the new
stack. Note that this process will reverse the order of elements.
Listing 2.67: Code example

1 // Example : Copying one stack to another


2

3 Stack < int > original = new Stack < int >() ;
4 original . Push (1) ;
5 original . Push (2) ;
6

7 Stack < int > copy = new Stack < int >( original ) ; // The copy will have the same
elements in LIFO order

How do you implement a stack using an array or list in .NET?


You can implement a custom stack using an array or list by using a dynamic array (List<T>) for
flexibility. Use Add() and RemoveAt() (or Pop() logic) to replicate stack behavior.
Listing 2.68: Code example

1 // Example : Implementing a custom stack using List <T >


2

3 class CustomStack <T >


4 {
5 private List <T > list = new List <T >() ;
6

7 public void Push ( T item )


8 {
9 list . Add ( item ) ;
10 }
11

12 public T Pop ()
13 {
14 if ( list . Count == 0)
15 throw new InvalidOperationException ( " Stack is empty " ) ;
16

17 T item = list [ list . Count - 1];


18 list . RemoveAt ( list . Count - 1) ;
19 return item ;
20 }
21

22 public T Peek ()
23 {
24 if ( list . Count == 0)
25 throw new InvalidOperationException ( " Stack is empty " ) ;

50
2.4. Stacks in .NET

26

27 return list [ list . Count - 1];


28 }
29 }

How do you ensure thread safety when using a stack in a multi-threaded


environment in .NET?
To ensure thread safety, use the ConcurrentStack<T> class from System.Collections.Concurrent,
which is designed for multi-threaded environments and provides lock-free operations.

Listing 2.69: Code example

1 // Example : Using ConcurrentStack for thread - safe stack operations


2

3 ConcurrentStack < int > concurrentStack = new ConcurrentStack < int >() ;
4 concurrentStack . Push (1) ;
5 concurrentStack . Push (2) ;
6

7 int result ;
8 if ( concurrentStack . TryPop ( out result ) )
9 {
10 Console . WriteLine ( $ " Popped : { result } " ) ;
11 }

How do you limit the size of a stack in .NET?


You can limit the size of a stack by creating a custom stack class that checks the count of elements
before pushing. If the stack exceeds a certain size, throw an exception or handle it appropriately.

Listing 2.70: Code example

1 // Example : Creating a stack with size limit


2

3 class LimitedStack <T >


4 {
5 private Stack <T > stack = new Stack <T >() ;
6 private int maxSize ;
7

8 public LimitedStack ( int maxSize )


9 {
10 this . maxSize = maxSize ;
11 }
12

13 public void Push ( T item )


14 {

51
2.4. Stacks in .NET

15 if ( stack . Count >= maxSize )


16 throw new InvalidOperationException ( " Stack is full " ) ;
17

18 stack . Push ( item ) ;


19 }
20

21 public T Pop ()
22 {
23 return stack . Pop () ;
24 }
25

26 public T Peek ()
27 {
28 return stack . Peek () ;
29 }
30 }

How do you handle exceptions when popping an empty stack in .NET?


If you attempt to Pop() from an empty stack, an InvalidOperationException will be thrown.
You should check if the stack is empty using the Count property before calling Pop().
Listing 2.71: Code example

1 // Example : Handling exceptions when popping an empty stack


2

3 Stack < int > stack = new Stack < int >() ;
4

5 try
6 {
7 int element = stack . Pop () ;
8 }
9 catch ( InvalidOperationException ex )
10 {
11 Console . WriteLine ( " Cannot pop from an empty stack " ) ;
12 }

How do you reverse a string using a stack in .NET?


You can reverse a string by pushing each character onto a stack and then popping them to rebuild
the string in reverse order.
Listing 2.72: Code example

1 // Example : Reversing a string using a stack


2

3 string input = " hello " ;

52
2.5. Queues in .NET

4 Stack < char > stack = new Stack < char >() ;
5

6 foreach ( char c in input )


7 {
8 stack . Push ( c ) ;
9 }
10

11 string reversed = new string ( stack . ToArray () ) ; // Reverse the string using stack
12 Console . WriteLine ( reversed ) ; // Outputs : " olleh "

What is the difference between Queue<T> and Stack<T> in .NET?


The primary difference between a Queue<T> and a Stack<T> is the order in which elements are
accessed. Stack<T> is LIFO (last-in, first-out), while Queue<T> is FIFO (first-in, first-out).

Listing 2.73: Code example

1 // Example : Stack vs Queue


2

3 Stack < int > stack = new Stack < int >() ;
4 Queue < int > queue = new Queue < int >() ;
5

6 stack . Push (1) ;


7 stack . Push (2) ;
8

9 queue . Enqueue (1) ;


10 queue . Enqueue (2) ;
11

12 int stackElement = stack . Pop () ; // 2 ( LIFO )


13 int queueElement = queue . Dequeue () ; // 1 ( FIFO )

2.5 Queues in .NET


What is a Queue<T> in .NET and how does it work?
A Queue<T> in .NET is a first-in, first-out (FIFO) collection where elements are added at the end
and removed from the front. It supports operations like Enqueue() to add elements and Dequeue()
to remove elements.

Listing 2.74: Code example

1 // Example : Creating a queue and performing operations


2

3 Queue < int > queue = new Queue < int >() ;
4 queue . Enqueue (1) ; // Adds 1 to the queue

53
2.5. Queues in .NET

5 queue . Enqueue (2) ; // Adds 2 to the queue


6

7 int firstElement = queue . Dequeue () ; // Removes and returns the first element (1)
8 int nextElement = queue . Peek () ; // Returns the first element (2) without removing
it

How does the Enqueue() method work in a queue?


The Enqueue() method adds an element to the end of the queue, making it the last element to be
dequeued.

Listing 2.75: Code example

1 // Example : Using Enqueue to add elements to a queue


2

3 Queue < string > queue = new Queue < string >() ;
4 queue . Enqueue ( " first " ) ;
5 queue . Enqueue ( " second " ) ;
6

7 Console . WriteLine ( queue . Peek () ) ; // Outputs : first

How do you remove the first element from a queue in .NET?


The Dequeue() method removes and returns the first element from the queue. If the queue is
empty, an InvalidOperationException is thrown.

Listing 2.76: Code example

1 // Example : Removing the first element using Dequeue


2

3 Queue < int > queue = new Queue < int >() ;
4 queue . Enqueue (1) ;
5 queue . Enqueue (2) ;
6

7 int removedElement = queue . Dequeue () ; // Removes and returns 1

What is the Peek() method in a queue and how does it differ from
Dequeue()?
The Peek() method returns the first element of the queue without removing it, whereas Dequeue()
removes and returns the first element.

Listing 2.77: Code example

1 // Example : Using Peek to view the first element


2

54
2.5. Queues in .NET

3 Queue < string > queue = new Queue < string >() ;
4 queue . Enqueue ( " first " ) ;
5 queue . Enqueue ( " second " ) ;
6

7 string firstElement = queue . Peek () ; // Returns " first " without removing it

How do you check if a queue is empty in .NET?


The Count property can be used to check if a queue is empty. Alternatively, you can check
queue.Count == 0.

How do you clear all elements from a queue in .NET?


The Clear() method removes all elements from the queue, leaving it empty.

Listing 2.78: Code example

1 // Example : Clearing a queue


2

3 Queue < string > queue = new Queue < string >() ;
4 queue . Enqueue ( " first " ) ;
5 queue . Enqueue ( " second " ) ;
6

7 queue . Clear () ; // Removes all elements , queue is now empty

How do you iterate over a queue in .NET?


You can iterate over a queue using a foreach loop. The iteration happens in FIFO order, meaning
the first element enqueued is iterated first.

Listing 2.79: Code example

1 // Example : Iterating over a queue


2

3 Queue < int > queue = new Queue < int >() ;
4 queue . Enqueue (1) ;
5 queue . Enqueue (2) ;
6 queue . Enqueue (3) ;
7

8 foreach ( int item in queue )


9 {
10 Console . WriteLine ( item ) ; // Outputs 1 , 2 , 3
11 }

55
2.5. Queues in .NET

How do you convert a queue to an array in .NET?


You can convert a queue to an array using the ToArray() method. The array will maintain the
FIFO order of the queue.

Listing 2.80: Code example

1 // Example : Converting a queue to an array


2

3 Queue < int > queue = new Queue < int >() ;
4 queue . Enqueue (1) ;
5 queue . Enqueue (2) ;
6

7 int [] array = queue . ToArray () ; // Returns [1 , 2]

How do you check if a queue contains a specific element in .NET?


The Contains() method checks whether a specific element exists in the queue. It returns true if
the element is found, false otherwise.

Listing 2.81: Code example

1 // Example : Checking if a queue contains a specific element


2

3 Queue < string > queue = new Queue < string >() ;
4 queue . Enqueue ( " first " ) ;
5 queue . Enqueue ( " second " ) ;
6

7 bool containsElement = queue . Contains ( " first " ) ; // Returns true

What is the time complexity of Enqueue(), Dequeue(), and Peek() in a


queue?
The time complexity of Enqueue(), Dequeue(), and Peek() operations in a queue is O(1) since
each operation involves adding, removing, or retrieving the first/last element.

Listing 2.82: Code example

1 // Example : Time complexity remains constant for basic queue operations


2

3 Queue < int > queue = new Queue < int >() ;
4 queue . Enqueue (10) ; // O (1)
5 int first = queue . Peek () ; // O (1)
6 int removed = queue . Dequeue () ; // O (1)

56
2.5. Queues in .NET

How do you reverse a queue in .NET?


You can reverse a queue by dequeuing its elements into a stack, then enqueuing the elements from
the stack back into the queue to reverse the order.

Listing 2.83: Code example

1 // Example : Reversing a queue


2

3 Queue < int > queue = new Queue < int >() ;
4 queue . Enqueue (1) ;
5 queue . Enqueue (2) ;
6 queue . Enqueue (3) ;
7

8 Stack < int > stack = new Stack < int >() ;
9

10 while ( queue . Count > 0)


11 {
12 stack . Push ( queue . Dequeue () ) ;
13 }
14

15 while ( stack . Count > 0)


16 {
17 queue . Enqueue ( stack . Pop () ) ; // Queue is now reversed
18 }

Can you use a queue with custom types in .NET?


Yes, you can use a Queue<T> with custom types. Ensure that your custom type implements equality
methods if you plan to use operations like Contains().

Listing 2.84: Code example

1 // Example : Using a queue with custom types


2

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

8 Queue < Person > personQueue = new Queue < Person >() ;
9 personQueue . Enqueue ( new Person { Name = " Alice " }) ;
10 personQueue . Enqueue ( new Person { Name = " Bob " }) ;
11

12 Person firstPerson = personQueue . Dequeue () ; // " Alice "

57
2.5. Queues in .NET

How do you copy one queue to another in .NET?


You can copy a queue to another queue by iterating over it and enqueuing elements into the new
queue.
Listing 2.85: Code example

1 // Example : Copying one queue to another


2

3 Queue < int > original = new Queue < int >() ;
4 original . Enqueue (1) ;
5 original . Enqueue (2) ;
6

7 Queue < int > copy = new Queue < int >( original ) ; // The copy will have the same
elements in FIFO order

How do you implement a queue using an array or list in .NET?


You can implement a custom queue using a dynamic array (List<T>) for flexibility. Use Add() to
enqueue and RemoveAt(0) to dequeue.
Listing 2.86: Code example

1 // Example : Implementing a custom queue using List <T >


2

3 class CustomQueue <T >


4 {
5 private List <T > list = new List <T >() ;
6

7 public void Enqueue ( T item )


8 {
9 list . Add ( item ) ;
10 }
11

12 public T Dequeue ()
13 {
14 if ( list . Count == 0)
15 throw new InvalidOperationException ( " Queue is empty " ) ;
16

17 T item = list [0];


18 list . RemoveAt (0) ;
19 return item ;
20 }
21

22 public T Peek ()
23 {
24 if ( list . Count == 0)
25 throw new InvalidOperationException ( " Queue is empty " ) ;

58
2.5. Queues in .NET

26

27 return list [0];


28 }
29 }

How do you ensure thread safety when using a queue in a multi-threaded


environment in .NET?
To ensure thread safety, use the ConcurrentQueue<T> class from System.Collections.Concurrent,
which provides lock-free thread-safe access.

Listing 2.87: Code example

1 // Example : Using ConcurrentQueue for thread - safe queue operations


2

3 ConcurrentQueue < int > concurrentQueue = new ConcurrentQueue < int >() ;
4 concurrentQueue . Enqueue (1) ;
5 concurrentQueue . Enqueue (2) ;
6

7 if ( concurrentQueue . TryDequeue ( out int result ) )


8 {
9 Console . WriteLine ( $ " Dequeued : { result } " ) ;
10 }

How do you limit the size of a queue in .NET?


You can limit the size of a queue by creating a custom queue class that checks the count of elements
before enqueuing. If the queue exceeds a certain size, throw an exception or handle it appropriately.

Listing 2.88: Code example

1 // Example : Creating a queue with size limit


2

3 class LimitedQueue <T >


4 {
5 private Queue <T > queue = new Queue <T >() ;
6 private int maxSize ;
7

8 public LimitedQueue ( int maxSize )


9 {
10 this . maxSize = maxSize ;
11 }
12

13 public void Enqueue ( T item )


14 {
15 if ( queue . Count >= maxSize )

59
2.5. Queues in .NET

16 throw new InvalidOperationException ( " Queue is full " ) ;


17

18 queue . Enqueue ( item ) ;


19 }
20

21 public T Dequeue ()
22 {
23 return queue . Dequeue () ;
24 }
25

26 public T Peek ()
27 {
28 return queue . Peek () ;
29 }
30 }

How do you handle exceptions when dequeuing an empty queue in .NET?


If you attempt to Dequeue() from an empty queue, an InvalidOperationException will be thrown.
You should check if the queue is empty using the Count property before calling Dequeue().
Listing 2.89: Code example

1 // Example : Handling exceptions when dequeuing an empty queue


2

3 Queue < int > queue = new Queue < int >() ;
4

5 try
6 {
7 int element = queue . Dequeue () ;
8 }
9 catch ( InvalidOperationException ex )
10 {
11 Console . WriteLine ( " Cannot dequeue from an empty queue " ) ;
12 }

How do you process a queue asynchronously in .NET?


You can process a queue asynchronously by dequeuing elements in a task-based or async/await
pattern, ensuring non-blocking operations while processing elements.
Listing 2.90: Code example

1 // Example : Processing a queue asynchronously


2

3 async Task ProcessQueueAsync ( Queue < int > queue )


4 {

60
2.6. HashTables and HashSets in .NET

5 while ( queue . Count > 0)


6 {
7 await Task . Delay (1000) ; // Simulate asynchronous work
8 int item = queue . Dequeue () ;
9 Console . WriteLine ( $ " Processed : { item } " ) ;
10 }
11 }
12

13 Queue < int > queue = new Queue < int >() ;
14 queue . Enqueue (1) ;
15 queue . Enqueue (2) ;
16 await ProcessQueueAsync ( queue ) ;

How do you reverse a string using a queue in .NET?


You can reverse a string using a queue by enqueuing each character and then rebuilding the reversed
string as you dequeue them.

Listing 2.91: Code example

1 // Example : Reversing a string using a queue


2

3 string input = " hello " ;


4 Queue < char > queue = new Queue < char >() ;
5

6 foreach ( char c in input )


7 {
8 queue . Enqueue ( c ) ;
9 }
10

11 Stack < char > stack = new Stack < char >() ;
12 while ( queue . Count > 0)
13 {
14 stack . Push ( queue . Dequeue () ) ;
15 }
16

17 string reversed = new string ( stack . ToArray () ) ;


18 Console . WriteLine ( reversed ) ; // Outputs " olleh "

2.6 HashTables and HashSets in .NET


What is a Hashtable in .NET and how does it work?
A Hashtable in .NET is a non-generic collection of key-value pairs that are organized based on the
hash code of the key. It allows fast lookups, but it does not preserve insertion order. A Hashtable

61
2.6. HashTables and HashSets in .NET

can hold objects of any data type.

Listing 2.92: Code example

1 // Example : Creating a Hashtable and performing operations


2

3 Hashtable hashtable = new Hashtable () ;


4 hashtable . Add ( " key1 " , " value1 " ) ;
5 hashtable . Add ( " key2 " , " value2 " ) ;
6

7 string value = ( string ) hashtable [ " key1 " ]; // Retrieves the value " value1 "

How does the Add() method work in a Hashtable in .NET?


The Add() method adds a key-value pair to the Hashtable. If the key already exists, it throws an
ArgumentException. Both key and value can be of any data type.

Listing 2.93: Code example

1 // Example : Adding key - value pairs to a Hashtable


2

3 Hashtable hashtable = new Hashtable () ;


4 hashtable . Add (1 , " Alice " ) ;
5 hashtable . Add (2 , " Bob " ) ;

How do you check if a key exists in a Hashtable in .NET?


The ContainsKey() method checks if a specific key exists in the Hashtable. It returns true if the
key is present, and false otherwise.

Listing 2.94: Code example

1 // Example : Checking if a key exists in a Hashtable


2

3 Hashtable hashtable = new Hashtable () ;


4 hashtable . Add (1 , " Alice " ) ;
5

6 bool keyExists = hashtable . ContainsKey (1) ; // Returns true

How do you remove an entry from a Hashtable in .NET?


The Remove() method removes the entry with the specified key from the Hashtable.

Listing 2.95: Code example

1 // Example : Removing an entry from a Hashtable


2

62
2.6. HashTables and HashSets in .NET

3 Hashtable hashtable = new Hashtable () ;


4 hashtable . Add (1 , " Alice " ) ;
5 hashtable . Remove (1) ; // Removes the entry with key 1

What is a HashSet<T> in .NET and how does it differ from a Hashtable?


A HashSet<T> is a generic collection that stores unique elements without any specific order. It
only stores values (no key-value pairs) and ensures no duplicates are present. A Hashtable stores
key-value pairs, whereas a HashSet<T> only stores values.

Listing 2.96: Code example

1 // Example : Creating a HashSet and adding elements


2

3 HashSet < int > hashSet = new HashSet < int >() ;
4 hashSet . Add (1) ;
5 hashSet . Add (2) ;
6 hashSet . Add (1) ; // Duplicate , not added
7

8 Console . WriteLine ( hashSet . Count ) ; // Outputs 2

How do you check if a HashSet<T> contains a specific element in .NET?


The Contains() method checks if a specific element exists in the HashSet<T>. It returns true if
the element is found, and false otherwise.

Listing 2.97: Code example

1 // Example : Checking if a HashSet contains a specific element


2

3 HashSet < string > hashSet = new HashSet < string >() ;
4 hashSet . Add ( " Alice " ) ;
5 hashSet . Add ( " Bob " ) ;
6

7 bool containsElement = hashSet . Contains ( " Alice " ) ; // Returns true

How do you remove an element from a HashSet<T> in .NET?


The Remove() method removes the specified element from the HashSet<T>. If the element exists,
it is removed; otherwise, the method returns false.

Listing 2.98: Code example

1 // Example : Removing an element from a HashSet


2

3 HashSet < int > hashSet = new HashSet < int >() ;

63
2.6. HashTables and HashSets in .NET

4 hashSet . Add (1) ;


5 hashSet . Add (2) ;
6

7 bool removed = hashSet . Remove (1) ; // Returns true and removes the element

How do you perform a union of two HashSet<T> collections in .NET?


You can perform a union of two HashSet<T> collections using the UnionWith() method, which
combines the elements from both sets, ensuring no duplicates.

Listing 2.99: Code example

1 // Example : Union of two HashSets


2

3 HashSet < int > set1 = new HashSet < int > { 1 , 2 , 3 };
4 HashSet < int > set2 = new HashSet < int > { 3 , 4 , 5 };
5

6 set1 . UnionWith ( set2 ) ; // set1 now contains { 1 , 2 , 3 , 4 , 5 }

How do you perform an intersection of two HashSet<T> collections in


.NET?
The IntersectWith() method keeps only the elements that are common between two HashSet<T>
collections, effectively performing an intersection.

Listing 2.100: Code example

1 // Example : Intersection of two HashSets


2

3 HashSet < int > set1 = new HashSet < int > { 1 , 2 , 3 };
4 HashSet < int > set2 = new HashSet < int > { 2 , 3 , 4 };
5

6 set1 . IntersectWith ( set2 ) ; // set1 now contains { 2 , 3 }

How do you perform a set difference between two HashSet<T> collections


in .NET?
The ExceptWith() method removes all elements in the first set that are also in the second set,
effectively performing a set difference.

Listing 2.101: Code example

1 // Example : Set difference between two HashSets


2

3 HashSet < int > set1 = new HashSet < int > { 1 , 2 , 3 };

64
2.6. HashTables and HashSets in .NET

4 HashSet < int > set2 = new HashSet < int > { 2 , 3 , 4 };
5

6 set1 . ExceptWith ( set2 ) ; // set1 now contains { 1 }

What is the time complexity of Add(), Remove(), Contains() in HashSet<T>


and Hashtable?
The time complexity for Add(), Remove(), and Contains() in both HashSet<T> and Hashtable is
O(1) on average because both collections use a hash-based implementation.

Listing 2.102: Code example

1 // Example : Time complexity remains O (1) for basic operations in HashSet and
Hashtable
2

3 HashSet < int > hashSet = new HashSet < int >() ;
4 hashSet . Add (1) ; // O (1)
5 bool contains = hashSet . Contains (1) ; // O (1)
6 bool removed = hashSet . Remove (1) ; // O (1)

How do you convert a Hashtable to a dictionary in .NET?


You can convert a Hashtable to a Dictionary<TKey, TValue> by iterating over the entries in the
Hashtable and adding them to the Dictionary<TKey, TValue>.

Listing 2.103: Code example

1 // Example : Converting a Hashtable to a Dictionary


2

3 Hashtable hashtable = new Hashtable () ;


4 hashtable . Add (1 , " Alice " ) ;
5 hashtable . Add (2 , " Bob " ) ;
6

7 Dictionary < int , string > dictionary = new Dictionary < int , string >() ;
8

9 foreach ( DictionaryEntry entry in hashtable )


10 {
11 dictionary . Add (( int ) entry . Key , ( string ) entry . Value ) ;
12 }

How do you ensure thread-safe operations on a Hashtable in .NET?


You can use the Hashtable.Synchronized() method to obtain a thread-safe wrapper around a
Hashtable. This ensures that all operations are synchronized.

65
2.6. HashTables and HashSets in .NET

Listing 2.104: Code example

1 // Example : Making a Hashtable thread - safe


2

3 Hashtable hashtable = new Hashtable () ;


4 Hashtable syncHashtable = Hashtable . Synchronized ( hashtable ) ;
5

6 syncHashtable . Add (1 , " Alice " ) ; // Thread - safe operation

How do you compare two HashSet<T> collections in .NET?


You can compare two HashSet<T> collections using methods like SetEquals(), which checks if
both sets contain the same elements.

Listing 2.105: Code example

1 // Example : Comparing two HashSets


2

3 HashSet < int > set1 = new HashSet < int > { 1 , 2 , 3 };
4 HashSet < int > set2 = new HashSet < int > { 1 , 2 , 3 };
5

6 bool areEqual = set1 . SetEquals ( set2 ) ; // Returns true

How do you convert a HashSet<T> to a list or an array in .NET?


You can convert a HashSet<T> to a list using the ToList() method or to an array using the
ToArray() method from LINQ.

Listing 2.106: Code example

1 // Example : Converting a HashSet to a List or Array


2

3 HashSet < int > hashSet = new HashSet < int > { 1 , 2 , 3 };
4

5 List < int > list = hashSet . ToList () ; // Converts to a List


6 int [] array = hashSet . ToArray () ; // Converts to an Array

How do you create a read-only HashSet<T> in .NET?


You can create a read-only HashSet<T> using the ReadOnlyCollection wrapper. However,
HashSet<T> itself does not have a built-in read-only option.

Listing 2.107: Code example

1 // Example : Creating a read - only HashSet


2

3 HashSet < int > hashSet = new HashSet < int > { 1 , 2 , 3 };

66
2.6. HashTables and HashSets in .NET

4 IReadOnlyCollection < int > readOnlyHashSet = new ReadOnlyCollection < int >( hashSet .
ToList () ) ;

How do you handle exceptions when trying to add a duplicate key in a


Hashtable?
If you attempt to add a duplicate key in a Hashtable, an ArgumentException will be thrown. To
avoid this, you can check if the key exists using ContainsKey() before adding the entry.

Listing 2.108: Code example

1 // Example : Handling exceptions when adding duplicate keys in a Hashtable


2

3 Hashtable hashtable = new Hashtable () ;


4 hashtable . Add (1 , " Alice " ) ;
5

6 if (! hashtable . ContainsKey (1) )


7 {
8 hashtable . Add (1 , " Bob " ) ; // Won ’t be added
9 }
10 else
11 {
12 Console . WriteLine ( " Key already exists " ) ;
13 }

How do you handle exceptions when adding duplicate values in a


HashSet<T>?
When adding a duplicate value to a HashSet<T>, it simply ignores the operation and does not
throw an exception. You can check the return value of Add() to see if the element was added
successfully.

Listing 2.109: Code example

1 // Example : Handling duplicate values in a HashSet


2

3 HashSet < int > hashSet = new HashSet < int >() ;
4 bool added = hashSet . Add (1) ; // Returns true
5 added = hashSet . Add (1) ; // Returns false , duplicate value not added

How do you check if a HashSet<T> is a subset of another set in .NET?


The IsSubsetOf() method checks if all elements of one HashSet<T> are contained within another
HashSet<T>.

67
2.6. HashTables and HashSets in .NET

Listing 2.110: Code example

1 // Example : Checking if a set is a subset


2

3 HashSet < int > set1 = new HashSet < int > { 1 , 2 };
4 HashSet < int > set2 = new HashSet < int > { 1 , 2 , 3 };
5

6 bool isSubset = set1 . IsSubsetOf ( set2 ) ; // Returns true

How do you check if a HashSet<T> is a superset of another set in .NET?


The IsSupersetOf() method checks if the current HashSet<T> contains all elements of another
HashSet<T>.
Listing 2.111: Code example

1 // Example : Checking if a set is a superset


2

3 HashSet < int > set1 = new HashSet < int > { 1 , 2 , 3 };
4 HashSet < int > set2 = new HashSet < int > { 1 , 2 };
5

6 bool isSuperset = set1 . IsSupersetOf ( set2 ) ; // Returns true

Linked Lists in .NET


What is a LinkedList<T> in .NET, and how does it work?
A LinkedList<T> in .NET is a doubly linked list, where each node contains a reference to both the
next and the previous node in the sequence. Unlike arrays or lists, it allows for efficient insertions
and deletions at both ends and in the middle of the list.
Listing 2.112: Code example

1 // Example : Creating a LinkedList and adding elements


2

3 LinkedList < int > linkedList = new LinkedList < int >() ;
4 linkedList . AddLast (1) ; // Adds 1 at the end
5 linkedList . AddLast (2) ; // Adds 2 at the end
6 linkedList . AddFirst (0) ; // Adds 0 at the beginning

How do you add elements at the beginning and end of a LinkedList<T> in


.NET?
You can use the AddFirst() method to add an element to the beginning and the AddLast() method
to add an element to the end of a LinkedList<T>.

68
2.6. HashTables and HashSets in .NET

Listing 2.113: Code example

1 // Example : Adding elements at the beginning and end


2

3 LinkedList < string > linkedList = new LinkedList < string >() ;
4 linkedList . AddFirst ( " first " ) ;
5 linkedList . AddLast ( " second " ) ;

How do you iterate over a LinkedList<T> in .NET?


You can iterate over a LinkedList<T> using a foreach loop, or you can manually traverse using
the First property and the Next reference in each node.
Listing 2.114: Code example

1 // Example : Iterating over a LinkedList


2

3 LinkedList < int > linkedList = new LinkedList < int >() ;
4 linkedList . AddLast (1) ;
5 linkedList . AddLast (2) ;
6 linkedList . AddLast (3) ;
7

8 foreach ( int value in linkedList )


9 {
10 Console . WriteLine ( value ) ; // Outputs : 1 , 2 , 3
11 }

How do you remove a specific node from a LinkedList<T> in .NET?


You can remove a specific node by passing the LinkedListNode<T> to the Remove() method, or
by specifying the value to remove.
Listing 2.115: Code example

1 // Example : Removing a specific node


2

3 LinkedList < int > linkedList = new LinkedList < int >() ;
4 linkedList . AddLast (1) ;
5 linkedList . AddLast (2) ;
6 linkedList . AddLast (3) ;
7

8 linkedList . Remove (2) ; // Removes the node with value 2

How do you access the first and last elements in a LinkedList<T> in .NET?
You can access the first element using the First property and the last element using the Last
property. These return LinkedListNode<T> objects.

69
2.6. HashTables and HashSets in .NET

Listing 2.116: Code example

1 // Example : Accessing the first and last elements


2

3 LinkedList < string > linkedList = new LinkedList < string >() ;
4 linkedList . AddLast ( " first " ) ;
5 linkedList . AddLast ( " second " ) ;
6

7 LinkedListNode < string > firstNode = linkedList . First ; // " first "
8 LinkedListNode < string > lastNode = linkedList . Last ; // " second "

How do you find a specific value in a LinkedList<T> in .NET?


You can find a specific value using the Find() method, which returns the LinkedListNode<T> if
found, or null if not found.

Listing 2.117: Code example

1 // Example : Finding a specific value in a LinkedList


2

3 LinkedList < int > linkedList = new LinkedList < int >() ;
4 linkedList . AddLast (1) ;
5 linkedList . AddLast (2) ;
6 linkedList . AddLast (3) ;
7

8 LinkedListNode < int > node = linkedList . Find (2) ; // Finds the node with value 2

How do you insert a new node before or after a specific node in a


LinkedList<T> in .NET?
You can insert a new node before a specific node using AddBefore() and after a specific node using
AddAfter().

Listing 2.118: Code example

1 // Example : Inserting a new node before or after a specific node


2

3 LinkedList < int > linkedList = new LinkedList < int >() ;
4 linkedList . AddLast (1) ;
5 linkedList . AddLast (3) ;
6

7 LinkedListNode < int > node = linkedList . Find (3) ;


8 linkedList . AddBefore ( node , 2) ; // Inserts 2 before 3
9 linkedList . AddAfter ( node , 4) ; // Inserts 4 after 3

70
2.6. HashTables and HashSets in .NET

How do you remove the first and last nodes from a LinkedList<T> in
.NET?
You can remove the first node using RemoveFirst() and the last node using RemoveLast().

Listing 2.119: Code example

1 // Example : Removing the first and last nodes


2

3 LinkedList < int > linkedList = new LinkedList < int >() ;
4 linkedList . AddLast (1) ;
5 linkedList . AddLast (2) ;
6 linkedList . AddLast (3) ;
7

8 linkedList . RemoveFirst () ; // Removes 1


9 linkedList . RemoveLast () ; // Removes 3

How do you clear all elements from a LinkedList<T> in .NET?


You can clear all the elements from a LinkedList<T> by using the Clear() method.

Listing 2.120: Code example

1 // Example : Clearing a LinkedList


2

3 LinkedList < int > linkedList = new LinkedList < int >() ;
4 linkedList . AddLast (1) ;
5 linkedList . AddLast (2) ;
6

7 linkedList . Clear () ; // Removes all elements

What is the time complexity of inserting or removing elements in a


LinkedList<T>?
The time complexity of inserting or removing elements in a LinkedList<T> at any position (be-
ginning, end, or middle) is O(1), as long as you have a reference to the node where the operation
is being performed.

Listing 2.121: Code example

1 // Example : Time complexity remains O (1) for insertion or removal operations


2

3 LinkedList < int > linkedList = new LinkedList < int >() ;
4 linkedList . AddLast (1) ; // O (1)
5 linkedList . RemoveLast () ; // O (1)

71
2.6. HashTables and HashSets in .NET

How does a LinkedList<T> differ from a List<T> in .NET?


A LinkedList<T> allows for efficient insertions and deletions at any position with O(1) complexity,
whereas a List<T> has O(n) complexity for insertions and deletions not at the end. A List<T>
provides faster access to elements by index, while LinkedList<T> does not support index-based
access.

Listing 2.122: Code example

1 // Example : Differences in insertion performance between List <T > and LinkedList <T >
2

3 List < int > list = new List < int >() ;
4 list . Insert (0 , 1) ; // O ( n ) for inserting at the beginning
5

6 LinkedList < int > linkedList = new LinkedList < int >() ;
7 linkedList . AddFirst (1) ; // O (1) for adding at the beginning

Can you use a LinkedList<T> with custom types in .NET?


Yes, you can use a LinkedList<T> with custom types. You can create a linked list of objects of
any class or struct type.

Listing 2.123: Code example

1 // Example : Using LinkedList with a custom type


2

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

8 LinkedList < Person > peopleList = new LinkedList < Person >() ;
9 peopleList . AddLast ( new Person { Name = " Alice " }) ;
10 peopleList . AddLast ( new Person { Name = " Bob " }) ;

How do you create a Circular LinkedList in .NET?


A LinkedList<T> in .NET is not circular by default, but you can implement a circular linked list
by manually linking the last node to the first node.

Listing 2.124: Code example

1 // Example : Creating a Circular LinkedList


2

3 LinkedList < int > linkedList = new LinkedList < int >() ;
4 linkedList . AddLast (1) ;
5 linkedList . AddLast (2) ;

72
2.6. HashTables and HashSets in .NET

6 linkedList . AddLast (3) ;


7

8 linkedList . Last . Next = linkedList . First ; // Create circular reference

How do you copy a LinkedList<T> to an array in .NET?


You can copy a LinkedList<T> to an array using the ToArray() extension method from LINQ or
by manually iterating and copying the elements.

Listing 2.125: Code example

1 // Example : Copying a LinkedList to an array


2

3 LinkedList < int > linkedList = new LinkedList < int >() ;
4 linkedList . AddLast (1) ;
5 linkedList . AddLast (2) ;
6 linkedList . AddLast (3) ;
7

8 int [] array = linkedList . ToArray () ; // Converts to array [1 , 2 , 3]

How do you traverse a LinkedList<T> from the last node to the first?
To traverse a LinkedList<T> from the last node to the first, you can use the Previous property
of LinkedListNode<T> starting from Last.

Listing 2.126: Code example

1 // Example : Traversing a LinkedList from last to first


2

3 LinkedList < int > linkedList = new LinkedList < int >() ;
4 linkedList . AddLast (1) ;
5 linkedList . AddLast (2) ;
6 linkedList . AddLast (3) ;
7

8 LinkedListNode < int > node = linkedList . Last ;


9 while ( node != null )
10 {
11 Console . WriteLine ( node . Value ) ; // Outputs : 3 , 2 , 1
12 node = node . Previous ;
13 }

How do you check if a LinkedList<T> contains a specific value in .NET?


You can check if a LinkedList<T> contains a specific value using the Contains() method, which
returns true if the value exists, false otherwise.

73
2.6. HashTables and HashSets in .NET

Listing 2.127: Code example

1 // Example : Checking if a LinkedList contains a specific value


2

3 LinkedList < int > linkedList = new LinkedList < int >() ;
4 linkedList . AddLast (1) ;
5 linkedList . AddLast (2) ;
6 linkedList . AddLast (3) ;
7

8 bool contains = linkedList . Contains (2) ; // Returns true

How do you convert a LinkedList<T> to a List<T> in .NET?


You can convert a LinkedList<T> to a List<T> by passing it to the constructor of List<T> or by
using the ToList() extension method from LINQ.

Listing 2.128: Code example

1 // Example : Converting a LinkedList to a List


2

3 LinkedList < int > linkedList = new LinkedList < int >() ;
4 linkedList . AddLast (1) ;
5 linkedList . AddLast (2) ;
6 linkedList . AddLast (3) ;
7

8 List < int > list = linkedList . ToList () ; // Converts to List [1 , 2 , 3]

How do you reverse a LinkedList<T> in .NET?


There is no built-in method to reverse a LinkedList<T>, but you can reverse it manually by
creating a new list and inserting nodes in reverse order.

Listing 2.129: Code example

1 // Example : Reversing a LinkedList


2

3 LinkedList < int > linkedList = new LinkedList < int >() ;
4 linkedList . AddLast (1) ;
5 linkedList . AddLast (2) ;
6 linkedList . AddLast (3) ;
7

8 LinkedList < int > reversedList = new LinkedList < int >() ;
9 LinkedListNode < int > node = linkedList . Last ;
10

11 while ( node != null )


12 {
13 reversedList . AddLast ( node . Value ) ;

74
2.7. Trees in .NET

14 node = node . Previous ;


15 }

How do you merge two LinkedList<T> collections in .NET?


You can merge two LinkedList<T> collections by iterating over one list and adding its elements
to the other list using AddLast().

Listing 2.130: Code example

1 // Example : Merging two LinkedLists


2

3 LinkedList < int > list1 = new LinkedList < int >() ;
4 list1 . AddLast (1) ;
5 list1 . AddLast (2) ;
6

7 LinkedList < int > list2 = new LinkedList < int >() ;
8 list2 . AddLast (3) ;
9 list2 . AddLast (4) ;
10

11 foreach ( int value in list2 )


12 {
13 list1 . AddLast ( value ) ; // Adds elements from list2 to list1
14 }

2.7 Trees in .NET


What is a binary tree, and how is it represented in .NET?
A binary tree is a data structure where each node has at most two children: a left child and a right
child. In .NET, a binary tree can be represented using a class where each node has references to
its left and right child nodes.

Listing 2.131: Code example

1 // Example : Binary tree representation


2

3 class TreeNode
4 {
5 public int Value ;
6 public TreeNode Left ;
7 public TreeNode Right ;
8

9 public TreeNode ( int value )


10 {

75
2.7. Trees in .NET

11 Value = value ;
12 Left = null ;
13 Right = null ;
14 }
15 }
16

17 // Creating a simple binary tree


18 TreeNode root = new TreeNode (1) ;
19 root . Left = new TreeNode (2) ;
20 root . Right = new TreeNode (3) ;

How do you perform in-order traversal in a binary tree in .NET?


In-order traversal visits the left subtree, the root node, and then the right subtree. It can be
implemented using recursion.
Listing 2.132: Code example

1 // Example : In - order traversal of a binary tree


2

3 void InOrder ( TreeNode node )


4 {
5 if ( node == null ) return ;
6

7 InOrder ( node . Left ) ; // Visit left subtree


8 Console . WriteLine ( node . Value ) ; // Visit root
9 InOrder ( node . Right ) ; // Visit right subtree
10 }
11

12 InOrder ( root ) ;

How do you perform pre-order traversal in a binary tree in .NET?


In pre-order traversal, the root node is visited first, followed by the left subtree, and then the right
subtree.
Listing 2.133: Code example

1 // Example : Pre - order traversal of a binary tree


2

3 void PreOrder ( TreeNode node )


4 {
5 if ( node == null ) return ;
6

7 Console . WriteLine ( node . Value ) ; // Visit root


8 PreOrder ( node . Left ) ; // Visit left subtree
9 PreOrder ( node . Right ) ; // Visit right subtree

76
2.7. Trees in .NET

10 }
11

12 PreOrder ( root ) ;

How do you perform post-order traversal in a binary tree in .NET?


In post-order traversal, the left subtree is visited first, followed by the right subtree, and finally
the root node.

Listing 2.134: Code example

1 // Example : Post - order traversal of a binary tree


2

3 void PostOrder ( TreeNode node )


4 {
5 if ( node == null ) return ;
6

7 PostOrder ( node . Left ) ; // Visit left subtree


8 PostOrder ( node . Right ) ; // Visit right subtree
9 Console . WriteLine ( node . Value ) ; // Visit root
10 }
11

12 PostOrder ( root ) ;

What is a balanced binary tree? How can it be achieved in .NET?


A balanced binary tree is one where the height of the left and right subtrees of any node differ by
no more than one. One way to maintain a balanced binary tree is by using a self-balancing tree
structure like an AVL tree or a Red-Black tree.

Listing 2.135: Code example

1 // Example : Checking if a binary tree is balanced


2

3 int Height ( TreeNode node )


4 {
5 if ( node == null ) return 0;
6

7 int leftHeight = Height ( node . Left ) ;


8 int rightHeight = Height ( node . Right ) ;
9

10 if ( Math . Abs ( leftHeight - rightHeight ) > 1) return -1; // Not balanced


11

12 return Math . Max ( leftHeight , rightHeight ) + 1;


13 }
14

77
2.7. Trees in .NET

15 bool IsBalanced ( TreeNode root )


16 {
17 return Height ( root ) != -1;
18 }

How do you find the height of a binary tree in .NET?


The height of a binary tree is the number of edges in the longest path from the root to a leaf. It
can be calculated recursively.
Listing 2.136: Code example

1 // Example : Finding the height of a binary tree


2

3 int FindHeight ( TreeNode node )


4 {
5 if ( node == null ) return -1; // Base case : empty tree
6

7 int leftHeight = FindHeight ( node . Left ) ;


8 int rightHeight = FindHeight ( node . Right ) ;
9

10 return Math . Max ( leftHeight , rightHeight ) + 1;


11 }
12

13 int height = FindHeight ( root ) ;


14 Console . WriteLine ( $ " Height of the tree : { height } " ) ;

How do you find the maximum value in a binary tree in .NET?


You can find the maximum value by recursively traversing the tree and comparing node values.
Listing 2.137: Code example

1 // Example : Finding the maximum value in a binary tree


2

3 int FindMax ( TreeNode node )


4 {
5 if ( node == null ) return int . MinValue ;
6

7 int leftMax = FindMax ( node . Left ) ;


8 int rightMax = FindMax ( node . Right ) ;
9

10 return Math . Max ( node . Value , Math . Max ( leftMax , rightMax ) ) ;


11 }
12

13 int maxValue = FindMax ( root ) ;


14 Console . WriteLine ( $ " Maximum value in the tree : { maxValue } " ) ;

78
2.7. Trees in .NET

How do you count the number of nodes in a binary tree in .NET?


The number of nodes in a binary tree can be counted by recursively visiting each node and incre-
menting a counter.

Listing 2.138: Code example

1 // Example : Counting the number of nodes in a binary tree


2

3 int CountNodes ( TreeNode node )


4 {
5 if ( node == null ) return 0;
6

7 return 1 + CountNodes ( node . Left ) + CountNodes ( node . Right ) ;


8 }
9

10 int totalNodes = CountNodes ( root ) ;


11 Console . WriteLine ( $ " Total nodes in the tree : { totalNodes } " ) ;

How do you find the lowest common ancestor (LCA) of two nodes in a
binary tree in .NET?
The LCA of two nodes in a binary tree is the lowest node that has both nodes as descendants.
You can find it by recursively checking where the nodes split.

Listing 2.139: Code example

1 // Example : Finding the lowest common ancestor ( LCA )


2

3 TreeNode FindLCA ( TreeNode root , TreeNode n1 , TreeNode n2 )


4 {
5 if ( root == null ) return null ;
6

7 if ( root == n1 || root == n2 ) return root ;


8

9 TreeNode leftLCA = FindLCA ( root . Left , n1 , n2 ) ;


10 TreeNode rightLCA = FindLCA ( root . Right , n1 , n2 ) ;
11

12 if ( leftLCA != null && rightLCA != null ) return root ;


13

14 return leftLCA != null ? leftLCA : rightLCA ;


15 }

79
2.7. Trees in .NET

How do you check if a binary tree is a binary search tree (BST) in .NET?
A binary tree is a BST if for each node, all elements in its left subtree are smaller, and all elements
in its right subtree are larger. This can be checked recursively by ensuring that each node falls
within a valid range.

Listing 2.140: Code example

1 // Example : Checking if a binary tree is a BST


2

3 bool IsBST ( TreeNode node , int ? min = null , int ? max = null )
4 {
5 if ( node == null ) return true ;
6

7 if (( min . HasValue && node . Value <= min ) || ( max . HasValue && node . Value >= max )
)
8 return false ;
9

10 return IsBST ( node . Left , min , node . Value ) && IsBST ( node . Right , node . Value , max )
;
11 }
12

13 bool isBST = IsBST ( root ) ;


14 Console . WriteLine ( $ " Is the tree a BST ? { isBST } " ) ;

How do you find the minimum depth of a binary tree in .NET?


The minimum depth is the number of nodes along the shortest path from the root node down to
the nearest leaf node. This can be calculated recursively.

Listing 2.141: Code example

1 // Example : Finding the minimum depth of a binary tree


2

3 int MinDepth ( TreeNode node )


4 {
5 if ( node == null ) return 0;
6

7 if ( node . Left == null && node . Right == null ) return 1;


8

9 if ( node . Left == null ) return MinDepth ( node . Right ) + 1;


10

11 if ( node . Right == null ) return MinDepth ( node . Left ) + 1;


12

13 return Math . Min ( MinDepth ( node . Left ) , MinDepth ( node . Right ) ) + 1;


14 }
15

80
2.7. Trees in .NET

16 int minDepth = MinDepth ( root ) ;


17 Console . WriteLine ( $ " Minimum depth of the tree : { minDepth } " ) ;

What is a complete binary tree, and how can you check if a binary tree
is complete in .NET?
A complete binary tree is one in which all levels are fully filled except possibly the last level,
which is filled from left to right. You can check for completeness by using level-order traversal and
ensuring no node after the first incomplete node has children.

Listing 2.142: Code example

1 // Example : Checking if a binary tree is complete


2

3 bool IsComplete ( TreeNode root )


4 {
5 if ( root == null ) return true ;
6

7 Queue < TreeNode > queue = new Queue < TreeNode >() ;
8 queue . Enqueue ( root ) ;
9 bool foundNull = false ;
10

11 while ( queue . Count > 0)


12 {
13 TreeNode current = queue . Dequeue () ;
14

15 if ( current == null )
16 {
17 foundNull = true ;
18 }
19 else
20 {
21 if ( foundNull ) return false ;
22

23 queue . Enqueue ( current . Left ) ;


24 queue . Enqueue ( current . Right ) ;
25 }
26 }
27 return true ;
28 }

How do you convert a binary tree to a doubly linked list in .NET?


You can convert a binary tree to a doubly linked list by performing an in-order traversal and
linking the nodes as you traverse.

81
2.7. Trees in .NET

Listing 2.143: Code example

1 // Example : Converting a binary tree to a doubly linked list


2

3 TreeNode ConvertToDLL ( TreeNode root , ref TreeNode prev )


4 {
5 if ( root == null ) return null ;
6

7 TreeNode head = ConvertToDLL ( root . Left , ref prev ) ;


8

9 if ( prev == null )
10 head = root ;
11 else
12 {
13 root . Left = prev ;
14 prev . Right = root ;
15 }
16 prev = root ;
17

18 ConvertToDLL ( root . Right , ref prev ) ;


19

20 return head ;
21 }
22

23 TreeNode prev = null ;


24 TreeNode head = ConvertToDLL ( root , ref prev ) ;

How do you check if two binary trees are identical in .NET?


Two binary trees are identical if they have the same structure and node values. You can check this
recursively by comparing corresponding nodes in both trees.
Listing 2.144: Code example

1 // Example : Checking if two binary trees are identical


2

3 bool AreIdentical ( TreeNode root1 , TreeNode root2 )


4 {
5 if ( root1 == null && root2 == null ) return true ;
6

7 if ( root1 == null || root2 == null ) return false ;


8

9 return root1 . Value == root2 . Value &&


10 AreIdentical ( root1 . Left , root2 . Left ) &&
11 AreIdentical ( root1 . Right , root2 . Right ) ;
12 }
13

14 bool identical = AreIdentical ( root , otherTreeRoot ) ;

82
2.7. Trees in .NET

15 Console . WriteLine ( $ " Are the two trees identical ? { identical } " ) ;

How do you calculate the diameter of a binary tree in .NET?


The diameter of a binary tree is the length of the longest path between any two nodes in the tree.
This path may or may not pass through the root.

Listing 2.145: Code example

1 // Example : Calculating the diameter of a binary tree


2

3 int CalculateDiameter ( TreeNode node , ref int diameter )


4 {
5 if ( node == null ) return 0;
6

7 int leftHeight = CalculateDiameter ( node . Left , ref diameter ) ;


8 int rightHeight = CalculateDiameter ( node . Right , ref diameter ) ;
9

10 diameter = Math . Max ( diameter , leftHeight + rightHeight ) ;


11

12 return Math . Max ( leftHeight , rightHeight ) + 1;


13 }
14

15 int diameter = 0;
16 CalculateDiameter ( root , ref diameter ) ;
17 Console . WriteLine ( $ " Diameter of the tree : { diameter } " ) ;

How do you perform level-order traversal (breadth-first search) on a bi-


nary tree in .NET?
Level-order traversal, also known as breadth-first search, visits nodes level by level, starting from
the root. It can be implemented using a queue.

Listing 2.146: Code example

1 // Example : Level - order traversal of a binary tree


2

3 void LevelOrderTraversal ( TreeNode root )


4 {
5 if ( root == null ) return ;
6

7 Queue < TreeNode > queue = new Queue < TreeNode >() ;
8 queue . Enqueue ( root ) ;
9

10 while ( queue . Count > 0)


11 {

83
2.8. Graphs in .NET

12 TreeNode node = queue . Dequeue () ;


13 Console . WriteLine ( node . Value ) ;
14

15 if ( node . Left != null ) queue . Enqueue ( node . Left ) ;


16 if ( node . Right != null ) queue . Enqueue ( node . Right ) ;
17 }
18 }
19

20 LevelOrderTraversal ( root ) ;

2.8 Graphs in .NET


What is a graph data structure, and how can it be represented in .NET?
A graph is a collection of nodes (vertices) connected by edges. It can be directed or undirected.
In .NET, graphs can be represented using adjacency lists, adjacency matrices, or dictionaries.

Listing 2.147: Code example

1 // Example : Graph representation using an adjacency list


2

3 class Graph
4 {
5 private Dictionary < int , List < int > > adjacencyList = new Dictionary < int , List <
int > >() ;
6

7 public void AddEdge ( int source , int destination )


8 {
9 if (! adjacencyList . ContainsKey ( source ) )
10 {
11 adjacencyList [ source ] = new List < int >() ;
12 }
13 adjacencyList [ source ]. Add ( destination ) ;
14 }
15

16 public List < int > GetNeighbors ( int node )


17 {
18 return adjacencyList . ContainsKey ( node ) ? adjacencyList [ node ] : new List <
int >() ;
19 }
20 }
21

22 // Creating a graph
23 Graph graph = new Graph () ;
24 graph . AddEdge (1 , 2) ;
25 graph . AddEdge (1 , 3) ;

84
2.8. Graphs in .NET

26 graph . AddEdge (2 , 4) ;

How do you perform a breadth-first search (BFS) in a graph in .NET?


Breadth-first search (BFS) explores the graph level by level. It uses a queue to traverse the graph,
starting from a given node and visiting all neighboring nodes before moving on to the next level.
Listing 2.148: Code example

1 // Example : BFS traversal in a graph


2

3 void BFS ( Graph graph , int start )


4 {
5 Queue < int > queue = new Queue < int >() ;
6 HashSet < int > visited = new HashSet < int >() ;
7

8 queue . Enqueue ( start ) ;


9 visited . Add ( start ) ;
10

11 while ( queue . Count > 0)


12 {
13 int node = queue . Dequeue () ;
14 Console . WriteLine ( node ) ;
15

16 foreach ( int neighbor in graph . GetNeighbors ( node ) )


17 {
18 if (! visited . Contains ( neighbor ) )
19 {
20 queue . Enqueue ( neighbor ) ;
21 visited . Add ( neighbor ) ;
22 }
23 }
24 }
25 }
26

27 BFS ( graph , 1) ;

How do you perform a depth-first search (DFS) in a graph in .NET?


Depth-first search (DFS) explores as far as possible along a branch before backtracking. It uses a
stack or recursion to explore the graph.
Listing 2.149: Code example

1 // Example : DFS traversal in a graph


2

3 void DFS ( Graph graph , int start , HashSet < int > visited )

85
2.8. Graphs in .NET

4 {
5 if ( visited . Contains ( start ) ) return ;
6

7 visited . Add ( start ) ;


8 Console . WriteLine ( start ) ;
9

10 foreach ( int neighbor in graph . GetNeighbors ( start ) )


11 {
12 DFS ( graph , neighbor , visited ) ;
13 }
14 }
15

16 HashSet < int > visited = new HashSet < int >() ;
17 DFS ( graph , 1 , visited ) ;

How do you detect a cycle in a directed graph in .NET?


To detect a cycle in a directed graph, you can use depth-first search (DFS) with a visited set and a
recursion stack. If you encounter a node that is already in the recursion stack, a cycle is detected.

Listing 2.150: Code example

1 // Example : Detecting a cycle in a directed graph using DFS


2

3 bool HasCycle ( Graph graph , int node , HashSet < int > visited , HashSet < int >
recursionStack )
4 {
5 if ( recursionStack . Contains ( node ) ) return true ;
6

7 if ( visited . Contains ( node ) ) return false ;


8

9 visited . Add ( node ) ;


10 recursionStack . Add ( node ) ;
11

12 foreach ( int neighbor in graph . GetNeighbors ( node ) )


13 {
14 if ( HasCycle ( graph , neighbor , visited , recursionStack ) )
15 {
16 return true ;
17 }
18 }
19

20 recursionStack . Remove ( node ) ;


21 return false ;
22 }
23

24 bool DetectCycle ( Graph graph )

86
2.8. Graphs in .NET

25 {
26 HashSet < int > visited = new HashSet < int >() ;
27 HashSet < int > recursionStack = new HashSet < int >() ;
28

29 foreach ( var node in graph . GetNeighbors (0) )


30 {
31 if ( HasCycle ( graph , node , visited , recursionStack ) ) return true ;
32 }
33 return false ;
34 }

How do you find the shortest path in an unweighted graph in .NET?


In an unweighted graph, the shortest path between two nodes can be found using breadth-first
search (BFS). BFS ensures that the first time you reach a node, it is via the shortest path.

Listing 2.151: Code example

1 // Example : Finding the shortest path in an unweighted graph using BFS


2

3 int [] FindShortestPath ( Graph graph , int start , int target )


4 {
5 Dictionary < int , int > parents = new Dictionary < int , int >() ;
6 Queue < int > queue = new Queue < int >() ;
7 HashSet < int > visited = new HashSet < int >() ;
8

9 queue . Enqueue ( start ) ;


10 visited . Add ( start ) ;
11 parents [ start ] = -1;
12

13 while ( queue . Count > 0)


14 {
15 int node = queue . Dequeue () ;
16

17 if ( node == target )
18 {
19 List < int > path = new List < int >() ;
20 for ( int current = target ; current != -1; current = parents [ current ])
21 {
22 path . Add ( current ) ;
23 }
24 path . Reverse () ;
25 return path . ToArray () ;
26 }
27

28 foreach ( int neighbor in graph . GetNeighbors ( node ) )


29 {

87
2.8. Graphs in .NET

30 if (! visited . Contains ( neighbor ) )


31 {
32 queue . Enqueue ( neighbor ) ;
33 visited . Add ( neighbor ) ;
34 parents [ neighbor ] = node ;
35 }
36 }
37 }
38

39 return new int [0]; // No path found


40 }
41

42 int [] shortestPath = FindShortestPath ( graph , 1 , 4) ;


43 Console . WriteLine ( string . Join ( " -> " , shortestPath ) ) ;

How do you implement Dijkstra’s algorithm for the shortest path in a


weighted graph in .NET?
Dijkstra’s algorithm finds the shortest path in a weighted graph by continuously picking the un-
visited node with the smallest distance. It uses a priority queue to efficiently fetch the next node.

Listing 2.152: Code example

1 // Example : Dijkstra ’s algorithm for shortest path in a weighted graph


2

3 class WeightedGraph
4 {
5 private Dictionary < int , List < Tuple < int , int > > > adjacencyList = new Dictionary <
int , List < Tuple < int , int > > >() ;
6

7 public void AddEdge ( int source , int destination , int weight )


8 {
9 if (! adjacencyList . ContainsKey ( source ) )
10 {
11 adjacencyList [ source ] = new List < Tuple < int , int > >() ;
12 }
13 adjacencyList [ source ]. Add ( Tuple . Create ( destination , weight ) ) ;
14 }
15

16 public List < Tuple < int , int > > GetNeighbors ( int node )
17 {
18 return adjacencyList . ContainsKey ( node ) ? adjacencyList [ node ] : new List <
Tuple < int , int > >() ;
19 }
20 }
21

88
2.8. Graphs in .NET

22 int [] Dijkstra ( WeightedGraph graph , int source , int target )


23 {
24 Dictionary < int , int > distances = new Dictionary < int , int >() ;
25 Dictionary < int , int > parents = new Dictionary < int , int >() ;
26 HashSet < int > visited = new HashSet < int >() ;
27 PriorityQueue < int , int > pq = new PriorityQueue < int , int >() ;
28

29 pq . Enqueue ( source , 0) ;
30 distances [ source ] = 0;
31 parents [ source ] = -1;
32

33 while ( pq . Count > 0)


34 {
35 int current = pq . Dequeue () ;
36

37 if ( visited . Contains ( current ) ) continue ;


38

39 visited . Add ( current ) ;


40

41 foreach ( var neighbor in graph . GetNeighbors ( current ) )


42 {
43 int destination = neighbor . Item1 ;
44 int weight = neighbor . Item2 ;
45 int newDist = distances [ current ] + weight ;
46

47 if (! distances . ContainsKey ( destination ) || newDist < distances [


destination ])
48 {
49 distances [ destination ] = newDist ;
50 parents [ destination ] = current ;
51 pq . Enqueue ( destination , newDist ) ;
52 }
53 }
54 }
55

56 if (! distances . ContainsKey ( target ) ) return new int [0];


57

58 List < int > path = new List < int >() ;
59 for ( int current = target ; current != -1; current = parents [ current ])
60 {
61 path . Add ( current ) ;
62 }
63 path . Reverse () ;
64 return path . ToArray () ;
65 }
66

67 WeightedGraph weightedGraph = new WeightedGraph () ;

89
2.8. Graphs in .NET

68 weightedGraph . AddEdge (1 , 2, 1) ;
69 weightedGraph . AddEdge (1 , 3, 4) ;
70 weightedGraph . AddEdge (2 , 3, 2) ;
71 weightedGraph . AddEdge (3 , 4, 1) ;
72

73 int [] dijkstraPath = Dijkstra ( weightedGraph , 1 , 4) ;


74 Console . WriteLine ( string . Join ( " -> " , dijkstraPath ) ) ;

How do you perform topological sorting on a directed acyclic graph


(DAG) in .NET?
Topological sorting orders the vertices of a directed acyclic graph (DAG) in such a way that for
every directed edge (u -> v), vertex u comes before v. This can be implemented using depth-first
search (DFS).

Listing 2.153: Code example

1 // Example : Topological sorting of a DAG using DFS


2

3 void TopologicalSortUtil ( Graph graph , int node , HashSet < int > visited , Stack < int >
stack )
4 {
5 visited . Add ( node ) ;
6

7 foreach ( int neighbor in graph . GetNeighbors ( node ) )


8 {
9 if (! visited . Contains ( neighbor ) )
10 {
11 TopologicalSortUtil ( graph , neighbor , visited , stack ) ;
12 }
13 }
14

15 stack . Push ( node ) ;


16 }
17

18 List < int > TopologicalSort ( Graph graph )


19 {
20 HashSet < int > visited = new HashSet < int >() ;
21 Stack < int > stack = new Stack < int >() ;
22

23 foreach ( var node in graph . GetNeighbors (0) )


24 {
25 if (! visited . Contains ( node ) )
26 {
27 TopologicalSortUtil ( graph , node , visited , stack ) ;
28 }

90
2.8. Graphs in .NET

29 }
30

31 List < int > sorted = new List < int >() ;
32 while ( stack . Count > 0)
33 {
34 sorted . Add ( stack . Pop () ) ;
35 }
36 return sorted ;
37 }
38

39 List < int > topologicalOrder = TopologicalSort ( graph ) ;


40 Console . WriteLine ( string . Join ( " -> " , topologicalOrder ) ) ;

How do you check if a graph is bipartite in .NET?


A graph is bipartite if it can be colored using two colors such that no two adjacent vertices have
the same color. This can be checked using BFS or DFS.

Listing 2.154: Code example

1 // Example : Checking if a graph is bipartite using BFS


2

3 bool IsBipartite ( Graph graph , int start )


4 {
5 Dictionary < int , int > colors = new Dictionary < int , int >() ;
6 Queue < int > queue = new Queue < int >() ;
7

8 queue . Enqueue ( start ) ;


9 colors [ start ] = 0;
10

11 while ( queue . Count > 0)


12 {
13 int node = queue . Dequeue () ;
14 int currentColor = colors [ node ];
15

16 foreach ( int neighbor in graph . GetNeighbors ( node ) )


17 {
18 if (! colors . ContainsKey ( neighbor ) )
19 {
20 colors [ neighbor ] = 1 - currentColor ; // Assign the opposite color
21 queue . Enqueue ( neighbor ) ;
22 }
23 else if ( colors [ neighbor ] == currentColor )
24 {
25 return false ; // Same color adjacent vertices , not bipartite
26 }
27 }

91
2.8. Graphs in .NET

28 }
29

30 return true ;
31 }
32

33 bool bipartite = IsBipartite ( graph , 1) ;


34 Console . WriteLine ( $ " Is the graph bipartite ? { bipartite } " ) ;

How do you implement Prim’s algorithm to find the minimum spanning


tree (MST) in .NET?
Prim’s algorithm finds the minimum spanning tree (MST) by starting from a node and expanding
the tree by adding the smallest edge that connects to a new node.

Listing 2.155: Code example

1 // Example : Prim ’s algorithm for finding the MST


2

3 class MSTGraph
4 {
5 private Dictionary < int , List < Tuple < int , int > > > adjacencyList = new Dictionary <
int , List < Tuple < int , int > > >() ;
6

7 public void AddEdge ( int source , int destination , int weight )


8 {
9 if (! adjacencyList . ContainsKey ( source ) )
10 {
11 adjacencyList [ source ] = new List < Tuple < int , int > >() ;
12 }
13 adjacencyList [ source ]. Add ( Tuple . Create ( destination , weight ) ) ;
14 }
15

16 public List < Tuple < int , int > > GetNeighbors ( int node )
17 {
18 return adjacencyList . ContainsKey ( node ) ? adjacencyList [ node ] : new List <
Tuple < int , int > >() ;
19 }
20 }
21

22 List < Tuple < int , int , int > > Prim ( MSTGraph graph , int start )
23 {
24 PriorityQueue < int , int > pq = new PriorityQueue < int , int >() ;
25 HashSet < int > visited = new HashSet < int >() ;
26 List < Tuple < int , int , int > > mst = new List < Tuple < int , int , int > >() ;
27

28 pq . Enqueue ( start , 0) ;

92
2.8. Graphs in .NET

29

30 while ( pq . Count > 0)


31 {
32 int current = pq . Dequeue () ;
33 visited . Add ( current ) ;
34

35 foreach ( var neighbor in graph . GetNeighbors ( current ) )


36 {
37 int destination = neighbor . Item1 ;
38 int weight = neighbor . Item2 ;
39

40 if (! visited . Contains ( destination ) )


41 {
42 pq . Enqueue ( destination , weight ) ;
43 mst . Add ( Tuple . Create ( current , destination , weight ) ) ;
44 }
45 }
46 }
47

48 return mst ;
49 }
50

51 MSTGraph mstGraph = new MSTGraph () ;


52 mstGraph . AddEdge (1 , 2 , 1) ;
53 mstGraph . AddEdge (1 , 3 , 4) ;
54 mstGraph . AddEdge (2 , 3 , 2) ;
55 mstGraph . AddEdge (3 , 4 , 1) ;
56

57 List < Tuple < int , int , int > > mst = Prim ( mstGraph , 1) ;
58 foreach ( var edge in mst )
59 {
60 Console . WriteLine ( $ " Edge : { edge . Item1 } -> { edge . Item2 } , Weight : { edge . Item3 } " )
;
61 }

How do you perform topological sorting in a directed acyclic graph


(DAG) in .NET?
Topological sorting orders the vertices of a directed acyclic graph (DAG) such that for every
directed edge u -> v, vertex u comes before v. It can be implemented using DFS or Kahn’s
algorithm.

Listing 2.156: Code example

1 // Example : Topological sorting using DFS


2

93
2.8. Graphs in .NET

3 void TopologicalSort ( Dictionary < int , List < int > > graph , int node , HashSet < int >
visited , Stack < int > stack )
4 {
5 visited . Add ( node ) ;
6

7 foreach ( var neighbor in graph [ node ])


8 {
9 if (! visited . Contains ( neighbor ) )
10 {
11 TopologicalSort ( graph , neighbor , visited , stack ) ;
12 }
13 }
14 stack . Push ( node ) ;
15 }
16

17 void PerformTopologicalSort ( Dictionary < int , List < int > > graph )
18 {
19 var stack = new Stack < int >() ;
20 var visited = new HashSet < int >() ;
21

22 foreach ( var node in graph . Keys )


23 {
24 if (! visited . Contains ( node ) )
25 {
26 TopologicalSort ( graph , node , visited , stack ) ;
27 }
28 }
29

30 while ( stack . Count > 0)


31 {
32 Console . Write ( stack . Pop () + " " ) ;
33 }
34 }
35

36 var graph = new Dictionary < int , List < int > >
37 {
38 { 5 , new List < int >{ 2 , 0 } } ,
39 { 4 , new List < int >{ 0 , 1 } } ,
40 { 3 , new List < int >{ 1 } } ,
41 { 2 , new List < int >{ 3 } } ,
42 { 1 , new List < int >() } ,
43 { 0 , new List < int >() }
44 };
45

46 PerformTopologicalSort ( graph ) ; // Output : Topological order

94
2.8. Graphs in .NET

How do you implement Bellman-Ford algorithm in .NET to find the short-


est path in a graph with negative weights?
The Bellman-Ford algorithm finds the shortest path from a single source in graphs that can have
negative-weight edges. It relaxes all edges up to |V| - 1 times and checks for negative weight
cycles.

Listing 2.157: Code example

1 // Example : Bellman - Ford algorithm implementation


2

3 bool BellmanFord ( Dictionary < int , List < Tuple < int , int > > > graph , int vertices , int
source , int [] distances )
4 {
5 Array . Fill ( distances , int . MaxValue ) ;
6 distances [ source ] = 0;
7

8 for ( int i = 1; i < vertices ; i ++)


9 {
10 foreach ( var u in graph . Keys )
11 {
12 foreach ( var (v , weight ) in graph [ u ])
13 {
14 if ( distances [ u ] != int . MaxValue && distances [ u ] + weight <
distances [ v ])
15 {
16 distances [ v ] = distances [ u ] + weight ;
17 }
18 }
19 }
20 }
21

22 foreach ( var u in graph . Keys )


23 {
24 foreach ( var (v , weight ) in graph [ u ])
25 {
26 if ( distances [ u ] != int . MaxValue && distances [ u ] + weight < distances [
v ])
27 {
28 Console . WriteLine ( " Graph contains negative weight cycle " ) ;
29 return false ;
30 }
31 }
32 }
33 return true ;
34 }
35

95
2.8. Graphs in .NET

36 var graph = new Dictionary < int , List < Tuple < int , int > > >()
37 {
38 { 0 , new List < Tuple < int , int > > { Tuple . Create (1 , -1) , Tuple . Create (2 , 4) } } ,
39 { 1 , new List < Tuple < int , int > > { Tuple . Create (2 , 3) , Tuple . Create (3 , 2) , Tuple
. Create (4 , 2) } } ,
40 { 3 , new List < Tuple < int , int > > { Tuple . Create (1 , 1) , Tuple . Create (2 , 5) } }
41 };
42

43 int [] distances = new int [5];


44 BellmanFord ( graph , 5 , 0 , distances ) ;

How do you find the shortest path in an unweighted graph in .NET?


In an unweighted graph, the shortest path between two vertices can be found using Breadth-First
Search (BFS) because BFS explores the nearest neighbors level by level.

Listing 2.158: Code example

1 // Example : Shortest path in an unweighted graph using BFS


2

3 void ShortestPathUnweighted ( Dictionary < int , List < int > > graph , int start , int end )
4 {
5 var queue = new Queue < int >() ;
6 var distances = new Dictionary < int , int > { { start , 0 } };
7 queue . Enqueue ( start ) ;
8

9 while ( queue . Count > 0)


10 {
11 int node = queue . Dequeue () ;
12

13 foreach ( var neighbor in graph [ node ])


14 {
15 if (! distances . ContainsKey ( neighbor ) )
16 {
17 distances [ neighbor ] = distances [ node ] + 1;
18 queue . Enqueue ( neighbor ) ;
19 if ( neighbor == end )
20 {
21 Console . WriteLine ( $ " Shortest path from { start } to { end } is {
distances [ neighbor ]} " ) ;
22 return ;
23 }
24 }
25 }
26 }
27 }
28

96
2.8. Graphs in .NET

29 var graph = new Dictionary < int , List < int > >
30 {
31 { 0 , new List < int >{ 1 , 3 } } ,
32 { 1 , new List < int >{ 0 , 2 , 3 } } ,
33 { 2 , new List < int >{ 1 , 3 } } ,
34 { 3 , new List < int >{ 0 , 1 , 2 } }
35 };
36

37 ShortestPathUnweighted ( graph , 0 , 2) ; // Output : Shortest path from 0 to 2 is 2

How do you detect a cycle in a directed graph in .NET?


In a directed graph, a cycle can be detected using Depth-First Search (DFS). If a node is visited
again while still in the recursion stack, then a cycle exists.

Listing 2.159: Code example

1 // Example : Cycle detection in a directed graph


2

3 bool HasCycleDirected ( Dictionary < int , List < int > > graph , int node , HashSet < int >
visited , HashSet < int > recursionStack )
4 {
5 visited . Add ( node ) ;
6 recursionStack . Add ( node ) ;
7

8 foreach ( var neighbor in graph [ node ])


9 {
10 if (! visited . Contains ( neighbor ) && HasCycleDirected ( graph , neighbor ,
visited , recursionStack ) )
11 return true ;
12 else if ( recursionStack . Contains ( neighbor ) )
13 return true ;
14 }
15

16 recursionStack . Remove ( node ) ;


17 return false ;
18 }
19

20 var graph = new Dictionary < int , List < int > >
21 {
22 { 0 , new List < int >{ 1 } } ,
23 { 1 , new List < int >{ 2 } } ,
24 { 2 , new List < int >{ 0 } }
25 };
26

27 var visited = new HashSet < int >() ;


28 var recursionStack = new HashSet < int >() ;

97
2.8. Graphs in .NET

29 Console . WriteLine ( HasCycleDirected ( graph , 0 , visited , recursionStack ) ) ; // Output


: true

How do you find all paths between two nodes in a graph in .NET?
To find all paths between two nodes, you can use DFS and backtrack to explore every possible
path.

Listing 2.160: Code example

1 // Example : Finding all paths between two nodes using DFS


2

3 void FindAllPaths ( Dictionary < int , List < int > > graph , int start , int end , List < int >
path )
4 {
5 path . Add ( start ) ;
6

7 if ( start == end )
8 {
9 Console . WriteLine ( string . Join ( " -> " , path ) ) ;
10 }
11 else
12 {
13 foreach ( var neighbor in graph [ start ])
14 {
15 if (! path . Contains ( neighbor ) )
16 {
17 FindAllPaths ( graph , neighbor , end , new List < int >( path ) ) ;
18 }
19 }
20 }
21 }
22

23 var graph = new Dictionary < int , List < int > >
24 {
25 { 0 , new List < int >{ 1 , 2 } } ,
26 { 1 , new List < int >{ 2 , 3 } } ,
27 { 2 , new List < int >{ 3 } } ,
28 { 3 , new List < int >() }
29 };
30

31 FindAllPaths ( graph , 0 , 3 , new List < int >() ) ; // Output : 0 -> 1 -> 3 , 0 -> 1 -> 2
-> 3 , 0 -> 2 -> 3

98
2.8. Graphs in .NET

How do you find the shortest path in a graph using bidirectional search
in .NET?
Bidirectional search runs two simultaneous BFSs—one from the source node and the other from
the destination node. When the two searches meet, the shortest path is found.

Listing 2.161: Code example

1 // Example : Bidirectional search for shortest path


2

3 bool BidirectionalBFS ( Dictionary < int , List < int > > graph , int start , int end )
4 {
5 var queueStart = new Queue < int >() ;
6 var queueEnd = new Queue < int >() ;
7 var visitedStart = new HashSet < int > { start };
8 var visitedEnd = new HashSet < int > { end };
9

10 queueStart . Enqueue ( start ) ;


11 queueEnd . Enqueue ( end ) ;
12

13 while ( queueStart . Count > 0 && queueEnd . Count > 0)


14 {
15 if ( BFSStep ( graph , queueStart , visitedStart , visitedEnd ) || BFSStep ( graph ,
queueEnd , visitedEnd , visitedStart ) )
16 return true ;
17 }
18 return false ;
19 }
20

21 bool BFSStep ( Dictionary < int , List < int > > graph , Queue < int > queue , HashSet < int >
visitedThisSide , HashSet < int > visitedOtherSide )
22 {
23 int node = queue . Dequeue () ;
24

25 foreach ( var neighbor in graph [ node ])


26 {
27 if ( visitedOtherSide . Contains ( neighbor ) )
28 return true ;
29

30 if (! visitedThisSide . Contains ( neighbor ) )


31 {
32 visitedThisSide . Add ( neighbor ) ;
33 queue . Enqueue ( neighbor ) ;
34 }
35 }
36 return false ;
37 }

99
2.8. Graphs in .NET

38

39 var graph = new Dictionary < int , List < int > >
40 {
41 { 0 , new List < int >{ 1 , 3 } } ,
42 { 1 , new List < int >{ 0 , 2 , 3 } } ,
43 { 2 , new List < int >{ 1 , 3 } } ,
44 { 3 , new List < int >{ 0 , 1 , 2 } }
45 };
46

47 Console . WriteLine ( BidirectionalBFS ( graph , 0 , 2) ) ; // Output : true

How do you perform the Edmonds-Karp algorithm for finding the maxi-
mum flow in a flow network in .NET?
The Edmonds-Karp algorithm is an implementation of the Ford-Fulkerson method for computing
the maximum flow in a flow network. It uses BFS to find augmenting paths.

Listing 2.162: Code example

1 // Example : Edmonds - Karp algorithm for maximum flow


2

3 int BFS ( int [ ,] rGraph , int s , int t , int [] parent )


4 {
5 bool [] visited = new bool [ rGraph . GetLength (0) ];
6 Queue < int > queue = new Queue < int >() ;
7 queue . Enqueue ( s ) ;
8 visited [ s ] = true ;
9 parent [ s ] = -1;
10

11 while ( queue . Count != 0)


12 {
13 int u = queue . Dequeue () ;
14 for ( int v = 0; v < rGraph . GetLength (0) ; v ++)
15 {
16 if (! visited [ v ] && rGraph [u , v ] > 0)
17 {
18 queue . Enqueue ( v ) ;
19 parent [ v ] = u ;
20 visited [ v ] = true ;
21 if ( v == t ) return true ;
22 }
23 }
24 }
25 return false ;
26 }
27

100
2.8. Graphs in .NET

28 int EdmondsKarp ( int [ ,] graph , int s , int t )


29 {
30 int u , v ;
31 int [ ,] rGraph = ( int [ ,]) graph . Clone () ;
32 int [] parent = new int [ graph . GetLength (0) ];
33 int maxFlow = 0;
34

35 while ( BFS ( rGraph , s , t , parent ) )


36 {
37 int pathFlow = int . MaxValue ;
38 for ( v = t ; v != s ; v = parent [ v ])
39 {
40 u = parent [ v ];
41 pathFlow = Math . Min ( pathFlow , rGraph [u , v ]) ;
42 }
43

44 for ( v = t ; v != s ; v = parent [ v ])
45 {
46 u = parent [ v ];
47 rGraph [u , v ] -= pathFlow ;
48 rGraph [v , u ] += pathFlow ;
49 }
50

51 maxFlow += pathFlow ;
52 }
53 return maxFlow ;
54 }
55

56 int [ ,] graph = new int [ ,]


57 {
58 {0 , 16 , 13 , 0 , 0 , 0} ,
59 {0 , 0 , 10 , 12 , 0 , 0} ,
60 {0 , 4 , 0 , 0 , 14 , 0} ,
61 {0 , 0 , 9 , 0 , 0 , 20} ,
62 {0 , 0 , 0 , 7 , 0 , 4} ,
63 {0 , 0 , 0 , 0 , 0 , 0}
64 };
65

66 Console . WriteLine ( EdmondsKarp ( graph , 0 , 5) ) ; // Output : Maximum flow is 23

How do you detect articulation points (cut vertices) in a graph in .NET?


Articulation points are vertices that, when removed, increase the number of connected components
in a graph. They can be detected using DFS by keeping track of discovery and low values of each
vertex.

101
2.8. Graphs in .NET

Listing 2.163: Code example

1 // Example : Articulation points detection


2

3 void FindArticulationPoints ( Dictionary < int , List < int > > graph , int u , bool []
visited , int [] disc , int [] low , int [] parent , bool [] ap , ref int time )
4 {
5 int children = 0;
6 visited [ u ] = true ;
7 disc [ u ] = low [ u ] = ++ time ;
8

9 foreach ( int v in graph [ u ])


10 {
11 if (! visited [ v ])
12 {
13 children ++;
14 parent [ v ] = u ;
15 FindArticulationPoints ( graph , v , visited , disc , low , parent , ap , ref
time ) ;
16

17 low [ u ] = Math . Min ( low [ u ] , low [ v ]) ;


18

19 if ( parent [ u ] == -1 && children > 1)


20 ap [ u ] = true ;
21

22 if ( parent [ u ] != -1 && low [ v ] >= disc [ u ])


23 ap [ u ] = true ;
24 }
25 else if ( v != parent [ u ])
26 {
27 low [ u ] = Math . Min ( low [ u ] , disc [ v ]) ;
28 }
29 }
30 }
31

32 void ArticulationPoints ( Dictionary < int , List < int > > graph )
33 {
34 int vertices = graph . Count ;
35 bool [] visited = new bool [ vertices ];
36 int [] disc = new int [ vertices ];
37 int [] low = new int [ vertices ];
38 int [] parent = new int [ vertices ];
39 bool [] ap = new bool [ vertices ];
40 int time = 0;
41

42 for ( int i = 0; i < vertices ; i ++)


43 if (! visited [ i ])

102
2.8. Graphs in .NET

44 FindArticulationPoints ( graph , i , visited , disc , low , parent , ap , ref


time ) ;
45

46 for ( int i = 0; i < vertices ; i ++)


47 if ( ap [ i ])
48 Console . WriteLine ( " Articulation point : " + i ) ;
49 }
50

51 var graph = new Dictionary < int , List < int > >
52 {
53 { 0 , new List < int >{ 1 , 2 } } ,
54 { 1 , new List < int >{ 0 , 3 , 4 } } ,
55 { 2 , new List < int >{ 0 , 5 } } ,
56 { 3 , new List < int >{ 1 } } ,
57 { 4 , new List < int >{ 1 } } ,
58 { 5 , new List < int >{ 2 , 6 , 7 } } ,
59 { 6 , new List < int >{ 5 } } ,
60 { 7 , new List < int >{ 5 } }
61 };
62

63 ArticulationPoints ( graph ) ;

How do you perform Prim’s algorithm for minimum spanning tree in


.NET?
Prim’s algorithm finds the minimum spanning tree (MST) for a graph by starting from an arbitrary
vertex and adding the shortest edge that connects the growing MST to a new vertex.

Listing 2.164: Code example

1 // Example : Prim ’s algorithm implementation


2

3 void PrimsAlgorithm ( int [ ,] graph , int vertices )


4 {
5 int [] parent = new int [ vertices ];
6 int [] key = new int [ vertices ];
7 bool [] mstSet = new bool [ vertices ];
8

9 for ( int i = 0; i < vertices ; i ++)


10 key [ i ] = int . MaxValue ;
11 key [0] = 0;
12 parent [0] = -1;
13

14 for ( int count = 0; count < vertices - 1; count ++)


15 {
16 int u = MinKey ( key , mstSet , vertices ) ;

103
2.8. Graphs in .NET

17 mstSet [ u ] = true ;
18

19 for ( int v = 0; v < vertices ; v ++)


20 {
21 if ( graph [u , v ] != 0 && ! mstSet [ v ] && graph [u , v ] < key [ v ])
22 {
23 parent [ v ] = u ;
24 key [ v ] = graph [u , v ];
25 }
26 }
27 }
28

29 PrintMST ( parent , graph , vertices ) ;


30 }
31

32 int MinKey ( int [] key , bool [] mstSet , int vertices )


33 {
34 int min = int . MaxValue , minIndex = -1;
35

36 for ( int v = 0; v < vertices ; v ++)


37 {
38 if (! mstSet [ v ] && key [ v ] < min )
39 {
40 min = key [ v ];
41 minIndex = v ;
42 }
43 }
44 return minIndex ;
45 }
46

47 void PrintMST ( int [] parent , int [ ,] graph , int vertices )


48 {
49 Console . WriteLine ( " Edge Weight " ) ;
50 for ( int i = 1; i < vertices ; i ++)
51 Console . WriteLine ( parent [ i ] + " - " + i + " " + graph [i , parent [ i ]]) ;
52 }
53

54 int [ ,] graph = new int [ ,]


55 {
56 { 0, 2, 0, 6, 0 },
57 { 2, 0, 3, 8, 5 },
58 { 0, 3, 0, 0, 7 },
59 { 6, 8, 0, 0, 9 },
60 { 0, 5, 7, 9, 0 }
61 };
62

63 PrimsAlgorithm ( graph , 5) ;

104
2.8. Graphs in .NET

How do you perform the Tarjan’s algorithm for finding strongly connected
components (SCC) in .NET?
Tarjan’s algorithm finds all strongly connected components (SCCs) in a directed graph. It uses
DFS and keeps track of the discovery time of each vertex and the lowest discovery time reachable
from the vertex.

Listing 2.165: Code example

1 // Example : Tarjan ’s algorithm for SCC


2

3 void SCCUtil ( Dictionary < int , List < int > > graph , int u , int [] disc , int [] low , Stack
< int > stack , bool [] stackMember , ref int time )
4 {
5 disc [ u ] = low [ u ] = ++ time ;
6 stack . Push ( u ) ;
7 stackMember [ u ] = true ;
8

9 foreach ( int v in graph [ u ])


10 {
11 if ( disc [ v ] == -1)
12 {
13 SCCUtil ( graph , v , disc , low , stack , stackMember , ref time ) ;
14 low [ u ] = Math . Min ( low [ u ] , low [ v ]) ;
15 }
16 else if ( stackMember [ v ])
17 {
18 low [ u ] = Math . Min ( low [ u ] , disc [ v ]) ;
19 }
20 }
21

22 int w = 0;
23 if ( low [ u ] == disc [ u ])
24 {
25 while ( stack . Peek () != u )
26 {
27 w = stack . Pop () ;
28 Console . Write ( w + " " ) ;
29 stackMember [ w ] = false ;
30 }
31 w = stack . Pop () ;
32 Console . Write ( w + " \ n " ) ;
33 stackMember [ w ] = false ;
34 }
35 }
36

37 void TarjanSCC ( Dictionary < int , List < int > > graph )

105
2.8. Graphs in .NET

38 {
39 int vertices = graph . Count ;
40 int [] disc = new int [ vertices ];
41 int [] low = new int [ vertices ];
42 bool [] stackMember = new bool [ vertices ];
43 Stack < int > stack = new Stack < int >() ;
44

45 Array . Fill ( disc , -1) ;


46 Array . Fill ( low , -1) ;
47

48 int time = 0;
49 for ( int i = 0; i < vertices ; i ++)
50 if ( disc [ i ] == -1)
51 SCCUtil ( graph , i , disc , low , stack , stackMember , ref time ) ;
52 }
53

54 var graph = new Dictionary < int , List < int > >
55 {
56 { 0 , new List < int >{ 1 } } ,
57 { 1 , new List < int >{ 2 } } ,
58 { 2 , new List < int >{ 0 , 3 } } ,
59 { 3 , new List < int >{ 4 } } ,
60 { 4 , new List < int >{ 5 , 7 } } ,
61 { 5 , new List < int >{ 6 } } ,
62 { 6 , new List < int >{ 4 } } ,
63 { 7 , new List < int >{ 8 } } ,
64 { 8 , new List < int >{ 9 } } ,
65 { 9 , new List < int >{ 7 } }
66 };
67

68 TarjanSCC ( graph ) ; // Output : SCCs of the graph

How do you find bridges (cut-edges) in a graph in .NET?


A bridge is an edge that, when removed, increases the number of connected components in a graph.
It can be found using DFS by tracking the discovery and lowest reachable time for each vertex.

Listing 2.166: Code example

1 // Example : Bridge detection using DFS


2

3 void BridgeUtil ( Dictionary < int , List < int > > graph , int u , bool [] visited , int []
disc , int [] low , int [] parent , ref int time )
4 {
5 visited [ u ] = true ;
6 disc [ u ] = low [ u ] = ++ time ;
7

106
2.8. Graphs in .NET

8 foreach ( int v in graph [ u ])


9 {
10 if (! visited [ v ])
11 {
12 parent [ v ] = u ;
13 BridgeUtil ( graph , v , visited , disc , low , parent , ref time ) ;
14

15 low [ u ] = Math . Min ( low [ u ] , low [ v ]) ;


16

17 if ( low [ v ] > disc [ u ])


18 Console . WriteLine ( u + " - " + v + " is a bridge " ) ;
19 }
20 else if ( v != parent [ u ])
21 {
22 low [ u ] = Math . Min ( low [ u ] , disc [ v ]) ;
23 }
24 }
25 }
26

27 void FindBridges ( Dictionary < int , List < int > > graph )
28 {
29 int vertices = graph . Count ;
30 bool [] visited = new bool [ vertices ];
31 int [] disc = new int [ vertices ];
32 int [] low = new int [ vertices ];
33 int [] parent = new int [ vertices ];
34 Array . Fill ( parent , -1) ;
35

36 int time = 0;
37 for ( int i = 0; i < vertices ; i ++)
38 if (! visited [ i ])
39 BridgeUtil ( graph , i , visited , disc , low , parent , ref time ) ;
40 }
41

42 var graph = new Dictionary < int , List < int > >
43 {
44 { 0 , new List < int >{ 1 } } ,
45 { 1 , new List < int >{ 0 , 2 , 3 } } ,
46 { 2 , new List < int >{ 1 , 3 } } ,
47 { 3 , new List < int >{ 1 , 2 , 4 } } ,
48 { 4 , new List < int >{ 3 , 5 } } ,
49 { 5 , new List < int >{ 4 } }
50 };
51

52 FindBridges ( graph ) ;

107
3 | Algorithms

Algorithms in C# are predefined steps or procedures that solve specific problems or accomplish
tasks. They work closely with data structures to process and manipulate information. Their pur-
pose ranges from basic operations, such as searching and sorting, to advanced functionalities that
optimize and streamline system performance.

Selecting the right algorithm can significantly influence an application’s efficiency. Different
tasks, like handling large data sets or performing complex operations, call for specific algorithmic
approaches. Knowing these approaches allows developers to address challenges effectively, reduce
computational overhead, and maintain clean, maintainable code.

Understanding C# algorithms also helps during collaboration and technical discussions. It


demonstrates a programmer’s capacity to reason about complexity, identify the most suitable
solution, and deliver software that meets performance and reliability standards.

3.1 Sorting Algorithms in .NET


What is the difference between ‘Array.Sort()‘ and ‘Array.OrderBy()‘ in
.NET?
‘Array.Sort()‘ is an in-place sorting algorithm that modifies the original array. ‘Array.OrderBy()‘
creates a new sorted collection without modifying the original array. ‘Array.Sort()‘ has better
performance because it doesn’t create a new collection, but ‘Array.OrderBy()‘ is more flexible as
it works with deferred execution in LINQ.

Listing 3.1: Code example

1 // Example : Difference between Array . Sort () and Array . OrderBy ()


2

3 int [] array = { 3 , 1 , 2 };
4 Array . Sort ( array ) ; // Sorts in place , array is now {1 , 2 , 3}
5

6 int [] newArray = array . OrderBy ( x = > x ) . ToArray () ; // Creates a new sorted array

108
3.1. Sorting Algorithms in .NET

How does quicksort work, and how can it be implemented in .NET?


Quicksort is a divide-and-conquer algorithm that works by selecting a ’pivot’ element and parti-
tioning the array into two halves, such that elements smaller than the pivot go to the left and
elements greater than the pivot go to the right. The process is recursively applied to the two
halves.

Listing 3.2: Code example

1 // Example : Quicksort implementation


2

3 void QuickSort ( int [] array , int low , int high )


4 {
5 if ( low < high )
6 {
7 int pivotIndex = Partition ( array , low , high ) ;
8 QuickSort ( array , low , pivotIndex - 1) ;
9 QuickSort ( array , pivotIndex + 1 , high ) ;
10 }
11 }
12

13 int Partition ( int [] array , int low , int high )


14 {
15 int pivot = array [ high ];
16 int i = low - 1;
17

18 for ( int j = low ; j < high ; j ++)


19 {
20 if ( array [ j ] <= pivot )
21 {
22 i ++;
23 Swap ( array , i , j ) ;
24 }
25 }
26 Swap ( array , i + 1 , high ) ;
27 return i + 1;
28 }
29

30 void Swap ( int [] array , int i , int j )


31 {
32 int temp = array [ i ];
33 array [ i ] = array [ j ];
34 array [ j ] = temp ;
35 }
36

37 int [] arr = { 3 , 6 , 8 , 10 , 1 , 2 , 1 };
38 QuickSort ( arr , 0 , arr . Length - 1) ;

109
3.1. Sorting Algorithms in .NET

How does merge sort work, and how can it be implemented in .NET?
Merge sort is a divide-and-conquer algorithm that divides the array into halves recursively, sorts
each half, and then merges the sorted halves. It is stable and works in O(n log n) time complexity.

Listing 3.3: Code example

1 // Example : Merge sort implementation


2

3 void MergeSort ( int [] array , int left , int right )


4 {
5 if ( left < right )
6 {
7 int middle = ( left + right ) / 2;
8 MergeSort ( array , left , middle ) ;
9 MergeSort ( array , middle + 1 , right ) ;
10 Merge ( array , left , middle , right ) ;
11 }
12 }
13

14 void Merge ( int [] array , int left , int middle , int right )
15 {
16 int n1 = middle - left + 1;
17 int n2 = right - middle ;
18 int [] leftArray = new int [ n1 ];
19 int [] rightArray = new int [ n2 ];
20

21 for ( int i = 0; i < n1 ; i ++) leftArray [ i ] = array [ left + i ];


22 for ( int j = 0; j < n2 ; j ++) rightArray [ j ] = array [ middle + 1 + j ];
23

24 int k = left , iLeft = 0 , iRight = 0;


25

26 while ( iLeft < n1 && iRight < n2 )


27 {
28 if ( leftArray [ iLeft ] <= rightArray [ iRight ])
29 {
30 array [ k ++] = leftArray [ iLeft ++];
31 }
32 else
33 {
34 array [ k ++] = rightArray [ iRight ++];
35 }
36 }
37

38 while ( iLeft < n1 ) array [ k ++] = leftArray [ iLeft ++];


39 while ( iRight < n2 ) array [ k ++] = rightArray [ iRight ++];
40 }
41

110
3.1. Sorting Algorithms in .NET

42 int [] arr = { 12 , 11 , 13 , 5 , 6 , 7 };
43 MergeSort ( arr , 0 , arr . Length - 1) ;

How does heap sort work, and how can it be implemented in .NET?
Heap sort is a comparison-based algorithm that uses a binary heap data structure. It builds a max
heap and repeatedly extracts the largest element, placing it at the end of the array. The process
continues until the array is sorted.

Listing 3.4: Code example

1 // Example : Heap sort implementation


2

3 void HeapSort ( int [] array )


4 {
5 int n = array . Length ;
6

7 for ( int i = n / 2 - 1; i >= 0; i - -) Heapify ( array , n , i ) ;


8 for ( int i = n - 1; i > 0; i - -)
9 {
10 Swap ( array , 0 , i ) ;
11 Heapify ( array , i , 0) ;
12 }
13 }
14

15 void Heapify ( int [] array , int n , int i )


16 {
17 int largest = i ;
18 int left = 2 * i + 1;
19 int right = 2 * i + 2;
20

21 if ( left < n && array [ left ] > array [ largest ]) largest = left ;
22 if ( right < n && array [ right ] > array [ largest ]) largest = right ;
23

24 if ( largest != i )
25 {
26 Swap ( array , i , largest ) ;
27 Heapify ( array , n , largest ) ;
28 }
29 }
30

31 int [] arr = { 12 , 11 , 13 , 5 , 6 , 7 };
32 HeapSort ( arr ) ;

111
3.1. Sorting Algorithms in .NET

How do you perform bubble sort in .NET, and why is it considered inef-
ficient?
Bubble sort repeatedly steps through the array, compares adjacent elements, and swaps them if
they are in the wrong order. It is inefficient with a time complexity of O(n2 ) because it compares
each pair of adjacent elements multiple times.

Listing 3.5: Code example

1 // Example : Bubble sort implementation


2

3 void BubbleSort ( int [] array )


4 {
5 int n = array . Length ;
6 for ( int i = 0; i < n - 1; i ++)
7 {
8 for ( int j = 0; j < n - i - 1; j ++)
9 {
10 if ( array [ j ] > array [ j + 1])
11 {
12 Swap ( array , j , j + 1) ;
13 }
14 }
15 }
16 }
17

18 int [] arr = { 64 , 34 , 25 , 12 , 22 , 11 , 90 };
19 BubbleSort ( arr ) ;

How does selection sort work in .NET?


Selection sort works by repeatedly selecting the smallest (or largest) element from the unsorted
portion of the array and swapping it with the first unsorted element. It has a time complexity of
O(n2 ) and is not stable.

Listing 3.6: Code example

1 // Example : Selection sort implementation


2

3 void SelectionSort ( int [] array )


4 {
5 int n = array . Length ;
6 for ( int i = 0; i < n - 1; i ++)
7 {
8 int minIndex = i ;
9 for ( int j = i + 1; j < n ; j ++)
10 {

112
3.1. Sorting Algorithms in .NET

11 if ( array [ j ] < array [ minIndex ])


12 {
13 minIndex = j ;
14 }
15 }
16 Swap ( array , i , minIndex ) ;
17 }
18 }
19

20 int [] arr = { 64 , 25 , 12 , 22 , 11 };
21 SelectionSort ( arr ) ;

How does insertion sort work, and how can it be implemented in .NET?
Insertion sort works by taking elements from the unsorted portion and inserting them into their
correct position in the sorted portion. It is simple to implement but has a time complexity of
O(n2 ) in the worst case.
Listing 3.7: Code example

1 // Example : Insertion sort implementation


2

3 void InsertionSort ( int [] array )


4 {
5 int n = array . Length ;
6 for ( int i = 1; i < n ; i ++)
7 {
8 int key = array [ i ];
9 int j = i - 1;
10

11 while ( j >= 0 && array [ j ] > key )


12 {
13 array [ j + 1] = array [ j ];
14 j - -;
15 }
16 array [ j + 1] = key ;
17 }
18 }
19

20 int [] arr = { 12 , 11 , 13 , 5 , 6 };
21 InsertionSort ( arr ) ;

What is the time complexity of quicksort in the average and worst cases?
Quicksort has an average-case time complexity of O(n log n) and a worst-case time complexity of
O(n2 ) when the pivot is chosen poorly (e.g., when the array is already sorted).

113
3.1. Sorting Algorithms in .NET

Listing 3.8: Code example

1 // Example : Quicksort time complexity


2

3 int [] arr = { 3 , 6 , 8 , 10 , 1 , 2 , 1 };
4 QuickSort ( arr , 0 , arr . Length - 1) ; // Average O ( n log n ) , worst O ( n ^2)

How do you perform counting sort in .NET?


Counting sort is a non-comparison sorting algorithm suitable for small integers. It counts the
occurrences of each unique value in the input array and uses these counts to place the elements in
the sorted order.

Listing 3.9: Code example

1 // Example : Counting sort implementation


2

3 void CountingSort ( int [] array , int maxVal )


4 {
5 int [] countArray = new int [ maxVal + 1];
6 for ( int i = 0; i < array . Length ; i ++)
7 {
8 countArray [ array [ i ]]++;
9 }
10

11 int index = 0;
12 for ( int i = 0; i < countArray . Length ; i ++)
13 {
14 while ( countArray [ i ] > 0)
15 {
16 array [ index ++] = i ;
17 countArray [ i ] - -;
18 }
19 }
20 }
21

22 int [] arr = { 4 , 2 , 2 , 8 , 3 , 3 , 1 };
23 CountingSort ( arr , 8) ;

How do you perform radix sort in .NET?


Radix sort sorts numbers digit by digit, starting from the least significant digit. It uses a stable
sorting algorithm like counting sort for each digit.

Listing 3.10: Code example

1 // Example : Radix sort implementation

114
3.1. Sorting Algorithms in .NET

3 void RadixSort ( int [] array )


4 {
5 int max = array . Max () ;
6 for ( int exp = 1; max / exp > 0; exp *= 10)
7 {
8 CountingSortByDigit ( array , exp ) ;
9 }
10 }
11

12 void CountingSortByDigit ( int [] array , int exp )


13 {
14 int n = array . Length ;
15 int [] output = new int [ n ];
16 int [] count = new int [10];
17

18 for ( int i = 0; i < n ; i ++) count [( array [ i ] / exp ) % 10]++;


19

20 for ( int i = 1; i < 10; i ++) count [ i ] += count [ i - 1];


21

22 for ( int i = n - 1; i >= 0; i - -)


23 {
24 output [ count [( array [ i ] / exp ) % 10] - 1] = array [ i ];
25 count [( array [ i ] / exp ) % 10] - -;
26 }
27

28 for ( int i = 0; i < n ; i ++) array [ i ] = output [ i ];


29 }
30

31 int [] arr = { 170 , 45 , 75 , 90 , 802 , 24 , 2 , 66 };


32 RadixSort ( arr ) ;

What is the difference between stable and unstable sorting algorithms?


A stable sorting algorithm maintains the relative order of records with equal keys (i.e., two equal
elements appear in the same order in the sorted output as they appeared in the input). An unstable
sorting algorithm does not guarantee this.

Listing 3.11: Code example

1 // Example : Stable vs . unstable sorting algorithms


2

3 // Merge sort is stable , quicksort is generally unstable

115
3.1. Sorting Algorithms in .NET

What is the time complexity of merge sort, and is it stable?


Merge sort has a time complexity of O(n log n) and is a stable sorting algorithm. It divides the
array into halves, recursively sorts each half, and then merges the sorted halves.

Listing 3.12: Code example

1 // Merge sort is O ( n log n ) and stable


2 int [] arr = { 12 , 11 , 13 , 5 , 6 , 7 };
3 MergeSort ( arr , 0 , arr . Length - 1) ;
4 // Output : Sorted array , stable sorting

How does bucket sort work, and what is its time complexity?
Bucket sort works by dividing the input into several buckets, sorting each bucket individually
(using a different sorting algorithm), and then concatenating all the buckets. Its time complexity
is O(n + k), where k is the number of buckets.

Listing 3.13: Code example

1 // Example : Bucket sort implementation


2

3 void BucketSort ( float [] array )


4 {
5 int n = array . Length ;
6 List < float >[] buckets = new List < float >[ n ];
7

8 for ( int i = 0; i < n ; i ++)


9 buckets [ i ] = new List < float >() ;
10

11 for ( int i = 0; i < n ; i ++)


12 {
13 int bucketIndex = ( int ) ( n * array [ i ]) ;
14 buckets [ bucketIndex ]. Add ( array [ i ]) ;
15 }
16

17 for ( int i = 0; i < n ; i ++)


18 buckets [ i ]. Sort () ;
19

20 int index = 0;
21 for ( int i = 0; i < n ; i ++)
22 {
23 foreach ( var item in buckets [ i ])
24 {
25 array [ index ++] = item ;
26 }
27 }

116
3.1. Sorting Algorithms in .NET

28 }
29

30 float [] arr = { 0.78 f , 0.17 f , 0.39 f , 0.26 f , 0.72 f };


31 BucketSort ( arr ) ; // Sorted array

How do you implement shell sort in .NET?


Shell sort is an optimization of insertion sort that allows the exchange of far-apart elements. It
works by initially sorting elements far apart from each other and progressively reducing the gap
between elements.

Listing 3.14: Code example

1 // Example : Shell sort implementation


2

3 void ShellSort ( int [] array )


4 {
5 int n = array . Length ;
6 for ( int gap = n / 2; gap > 0; gap /= 2)
7 {
8 for ( int i = gap ; i < n ; i ++)
9 {
10 int temp = array [ i ];
11 int j ;
12 for ( j = i ; j >= gap && array [ j - gap ] > temp ; j -= gap )
13 {
14 array [ j ] = array [ j - gap ];
15 }
16 array [ j ] = temp ;
17 }
18 }
19 }
20

21 int [] arr = { 12 , 34 , 54 , 2 , 3 };
22 ShellSort ( arr ) ; // Sorted array

What is the difference between comparison-based and non-comparison-


based sorting algorithms?
Comparison-based sorting algorithms like quicksort and mergesort rely on comparing elements to
determine their order, with a time complexity lower bound of O(n log n). Non-comparison-based
algorithms, such as counting sort, bucket sort, and radix sort, do not use comparisons and can
achieve linear time complexity for certain types of inputs.

Listing 3.15: Code example

117
3.1. Sorting Algorithms in .NET

1 // Comparison - based : Quicksort , MergeSort


2 // Non - comparison - based : CountingSort , BucketSort

How does tim sort work, and how is it used in .NET?


Tim sort is a hybrid stable sorting algorithm that combines merge sort and insertion sort. It works
well on real-world data by using insertion sort for small sections of the array and merge sort for
larger sections. In .NET, ‘List<T>.Sort()‘ and ‘Array.Sort()‘ use tim sort for certain types of
data.

Listing 3.16: Code example

1 // Tim sort is internally used in List <T >. Sort () and Array . Sort () for certain
datasets
2 int [] arr = { 5 , 4 , 3 , 2 , 1 };
3 Array . Sort ( arr ) ; // Uses tim sort

How do you implement gnome sort in .NET?


Gnome sort is a simple comparison-based sorting algorithm that is similar to insertion sort. It
works by comparing adjacent elements and swapping them if they are out of order, and then
stepping backward if needed.

Listing 3.17: Code example

1 // Example : Gnome sort implementation


2

3 void GnomeSort ( int [] array )


4 {
5 int index = 0;
6

7 while ( index < array . Length )


8 {
9 if ( index == 0 || array [ index ] >= array [ index - 1])
10 {
11 index ++;
12 }
13 else
14 {
15 int temp = array [ index ];
16 array [ index ] = array [ index - 1];
17 array [ index - 1] = temp ;
18 index - -;
19 }
20 }
21 }

118
3.1. Sorting Algorithms in .NET

22

23 int [] arr = { 34 , 2 , 78 , 45 , 20 };
24 GnomeSort ( arr ) ; // Sorted array

How do you implement cocktail shaker sort in .NET?


Cocktail shaker sort is a variation of bubble sort that passes through the list in both directions,
allowing smaller elements to move forward and larger elements to move backward.

Listing 3.18: Code example

1 // Example : Cocktail shaker sort implementation


2

3 void CocktailShakerSort ( int [] array )


4 {
5 bool swapped = true ;
6 int start = 0;
7 int end = array . Length - 1;
8

9 while ( swapped )
10 {
11 swapped = false ;
12

13 for ( int i = start ; i < end ; i ++)


14 {
15 if ( array [ i ] > array [ i + 1])
16 {
17 ( array [ i ] , array [ i + 1]) = ( array [ i + 1] , array [ i ]) ;
18 swapped = true ;
19 }
20 }
21

22 if (! swapped )
23 break ;
24

25 swapped = false ;
26 end - -;
27

28 for ( int i = end - 1; i >= start ; i - -)


29 {
30 if ( array [ i ] > array [ i + 1])
31 {
32 ( array [ i ] , array [ i + 1]) = ( array [ i + 1] , array [ i ]) ;
33 swapped = true ;
34 }
35 }
36

119
3.1. Sorting Algorithms in .NET

37 start ++;
38 }
39 }
40

41 int [] arr = { 5 , 1 , 4 , 2 , 8 };
42 CocktailShakerSort ( arr ) ; // Sorted array

What is the difference between merge sort and quicksort in .NET?


Merge sort is a stable, O(n log n) sorting algorithm that requires additional memory to store
temporary arrays. Quicksort is generally faster on average but is not stable and has a worst-
case time complexity of O(n²). Quicksort works in-place, while merge sort needs extra space for
merging.

Listing 3.19: Code example

1 // Merge sort vs . quicksort


2 // Merge sort is stable , quicksort is generally faster but unstable

How does heap sort work, and what is its time complexity?
Heap sort uses a binary heap to sort elements by repeatedly removing the largest element (for
max-heap) and restoring the heap property. It has a time complexity of O(n log n) and sorts the
array in-place.

Listing 3.20: Code example

1 // Example : Heap sort implementation


2

3 void HeapSort ( int [] array )


4 {
5 int n = array . Length ;
6

7 for ( int i = n / 2 - 1; i >= 0; i - -)


8 Heapify ( array , n , i ) ;
9

10 for ( int i = n - 1; i > 0; i - -)


11 {
12 ( array [0] , array [ i ]) = ( array [ i ] , array [0]) ;
13 Heapify ( array , i , 0) ;
14 }
15 }
16

17 void Heapify ( int [] array , int n , int i )


18 {
19 int largest = i ;

120
3.1. Sorting Algorithms in .NET

20 int left = 2 * i + 1;
21 int right = 2 * i + 2;
22

23 if ( left < n && array [ left ] > array [ largest ])


24 largest = left ;
25

26 if ( right < n && array [ right ] > array [ largest ])


27 largest = right ;
28

29 if ( largest != i )
30 {
31 ( array [ i ] , array [ largest ]) = ( array [ largest ] , array [ i ]) ;
32 Heapify ( array , n , largest ) ;
33 }
34 }
35

36 int [] arr = { 12 , 11 , 13 , 5 , 6 , 7 };
37 HeapSort ( arr ) ; // Sorted array

How do you implement pancake sort in .NET?


Pancake sort involves repeatedly finding the maximum element in the unsorted part of the array and
flipping the subarray to move the maximum element to its correct position. The time complexity
is O(n²).
Listing 3.21: Code example

1 // Example : Pancake sort implementation


2

3 void PancakeSort ( int [] array )


4 {
5 for ( int currSize = array . Length ; currSize > 1; currSize - -)
6 {
7 int maxIdx = FindMax ( array , currSize ) ;
8

9 if ( maxIdx != currSize - 1)
10 {
11 Flip ( array , maxIdx ) ;
12 Flip ( array , currSize - 1) ;
13 }
14 }
15 }
16

17 int FindMax ( int [] array , int n )


18 {
19 int maxIdx = 0;
20 for ( int i = 1; i < n ; i ++)

121
3.2. Search Algorithms in .NET

21 if ( array [ i ] > array [ maxIdx ])


22 maxIdx = i ;
23 return maxIdx ;
24 }
25

26 void Flip ( int [] array , int i )


27 {
28 int start = 0;
29 while ( start < i )
30 {
31 ( array [ start ] , array [ i ]) = ( array [ i ] , array [ start ]) ;
32 start ++;
33 i - -;
34 }
35 }
36

37 int [] arr = { 3 , 6 , 2 , 5 , 1 };
38 PancakeSort ( arr ) ; // Sorted array

3.2 Search Algorithms in .NET


How do you implement binary search in .NET?
Binary search is an efficient algorithm for finding an element in a sorted array. It works by dividing
the array in half, checking if the target element is equal to the middle element, and then repeating
the process in the half where the target element could be.
Listing 3.22: Code example

1 // Example : Binary search implementation


2

3 int BinarySearch ( int [] array , int target )


4 {
5 int left = 0;
6 int right = array . Length - 1;
7

8 while ( left <= right )


9 {
10 int mid = left + ( right - left ) / 2;
11

12 if ( array [ mid ] == target )


13 return mid ;
14 if ( array [ mid ] < target )
15 left = mid + 1;
16 else
17 right = mid - 1;

122
3.2. Search Algorithms in .NET

18 }
19

20 return -1; // Element not found


21 }
22

23 int [] arr = { 1 , 2 , 3 , 4 , 5 , 6 , 7 };
24 int result = BinarySearch ( arr , 5) ;
25 Console . WriteLine ( result ) ; // Output : 4

How do you implement linear search in .NET?


Linear search checks each element of an array sequentially to find the target element. It is simple
but inefficient for large datasets, as it runs in O(n) time complexity.
Listing 3.23: Code example

1 // Example : Linear search implementation


2

3 int LinearSearch ( int [] array , int target )


4 {
5 for ( int i = 0; i < array . Length ; i ++)
6 {
7 if ( array [ i ] == target )
8 return i ;
9 }
10 return -1; // Element not found
11 }
12

13 int [] arr = { 10 , 23 , 45 , 70 , 11 };
14 int result = LinearSearch ( arr , 70) ;
15 Console . WriteLine ( result ) ; // Output : 3

How do you implement depth-first search (DFS) on a graph in .NET?


DFS is a graph traversal algorithm that explores as far as possible along each branch before
backtracking. It can be implemented using recursion or an explicit stack.
Listing 3.24: Code example

1 // Example : DFS implementation


2

3 void DFS ( Dictionary < int , List < int > > graph , int node , HashSet < int > visited )
4 {
5 if ( visited . Contains ( node ) ) return ;
6

7 visited . Add ( node ) ;


8 Console . WriteLine ( node ) ;

123
3.2. Search Algorithms in .NET

10 foreach ( var neighbor in graph [ node ])


11 {
12 DFS ( graph , neighbor , visited ) ;
13 }
14 }
15

16 var graph = new Dictionary < int , List < int > >()
17 {
18 { 1 , new List < int >{ 2 , 3 } } ,
19 { 2 , new List < int >{ 4 , 5 } } ,
20 { 3 , new List < int >{ 6 , 7 } }
21 };
22

23 DFS ( graph , 1 , new HashSet < int >() ) ;

How do you implement breadth-first search (BFS) on a graph in .NET?


BFS is a graph traversal algorithm that explores all nodes at the present depth level before moving
on to the nodes at the next depth level. It is commonly implemented using a queue.

Listing 3.25: Code example

1 // Example : BFS implementation


2

3 void BFS ( Dictionary < int , List < int > > graph , int start )
4 {
5 var visited = new HashSet < int >() ;
6 var queue = new Queue < int >() ;
7

8 visited . Add ( start ) ;


9 queue . Enqueue ( start ) ;
10

11 while ( queue . Count > 0)


12 {
13 int node = queue . Dequeue () ;
14 Console . WriteLine ( node ) ;
15

16 foreach ( var neighbor in graph [ node ])


17 {
18 if (! visited . Contains ( neighbor ) )
19 {
20 visited . Add ( neighbor ) ;
21 queue . Enqueue ( neighbor ) ;
22 }
23 }
24 }

124
3.2. Search Algorithms in .NET

25 }
26

27 var graph = new Dictionary < int , List < int > >()
28 {
29 { 1 , new List < int >{ 2 , 3 } } ,
30 { 2 , new List < int >{ 4 , 5 } } ,
31 { 3 , new List < int >{ 6 , 7 } }
32 };
33

34 BFS ( graph , 1) ;

How do you implement jump search in .NET?


Jump search is an algorithm that searches for an element in a sorted array by jumping ahead by
fixed steps and then performing a linear search within the identified range. It is faster than linear
search but slower than binary search, with a time complexity of O(n).

Listing 3.26: Code example

1 // Example : Jump search implementation


2

3 int JumpSearch ( int [] array , int target )


4 {
5 int n = array . Length ;
6 int step = ( int ) Math . Sqrt ( n ) ;
7 int prev = 0;
8

9 while ( array [ Math . Min ( step , n ) - 1] < target )


10 {
11 prev = step ;
12 step += ( int ) Math . Sqrt ( n ) ;
13 if ( prev >= n ) return -1;
14 }
15

16 for ( int i = prev ; i < Math . Min ( step , n ) ; i ++)


17 {
18 if ( array [ i ] == target )
19 return i ;
20 }
21

22 return -1; // Element not found


23 }
24

25 int [] arr = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 };
26 int result = JumpSearch ( arr , 8) ;
27 Console . WriteLine ( result ) ; // Output : 8

125
3.2. Search Algorithms in .NET

How do you implement interpolation search in .NET?


Interpolation search improves on binary search for uniformly distributed data by estimating the
position of the target value. It calculates a probable position instead of always checking the middle
element.

Listing 3.27: Code example

1 // Example : Interpolation search implementation


2

3 int InterpolationSearch ( int [] array , int target )


4 {
5 int low = 0 , high = array . Length - 1;
6

7 while ( low <= high && target >= array [ low ] && target <= array [ high ])
8 {
9 int pos = low + (( target - array [ low ]) * ( high - low ) ) / ( array [ high ] -
array [ low ]) ;
10

11 if ( array [ pos ] == target )


12 return pos ;
13 if ( array [ pos ] < target )
14 low = pos + 1;
15 else
16 high = pos - 1;
17 }
18

19 return -1; // Element not found


20 }
21

22 int [] arr = { 10 , 20 , 30 , 40 , 50 , 60 , 70 , 80 , 90 };
23 int result = InterpolationSearch ( arr , 60) ;
24 Console . WriteLine ( result ) ; // Output : 5

How do you implement exponential search in .NET?


Exponential search is an algorithm that combines binary search and jump search. It works by
finding a range where the target value might lie by exponentially increasing the range and then
applying binary search within that range.

Listing 3.28: Code example

1 // Example : Exponential search implementation


2

3 int ExponentialSearch ( int [] array , int target )


4 {
5 if ( array [0] == target ) return 0;

126
3.2. Search Algorithms in .NET

7 int i = 1;
8 while ( i < array . Length && array [ i ] <= target )
9 i *= 2;
10

11 return BinarySearch ( array , i / 2 , Math . Min (i , array . Length - 1) , target ) ;


12 }
13

14 int BinarySearch ( int [] array , int left , int right , int target )
15 {
16 while ( left <= right )
17 {
18 int mid = left + ( right - left ) / 2;
19

20 if ( array [ mid ] == target )


21 return mid ;
22 if ( array [ mid ] < target )
23 left = mid + 1;
24 else
25 right = mid - 1;
26 }
27 return -1;
28 }
29

30 int [] arr = { 10 , 20 , 30 , 40 , 50 , 60 , 70 , 80 , 90 };
31 int result = ExponentialSearch ( arr , 40) ;
32 Console . WriteLine ( result ) ; // Output : 3

How do you perform search in a binary search tree (BST) in .NET?


Searching in a binary search tree (BST) involves starting at the root and traversing the tree based
on the comparison between the target value and the current node’s value. If the target is smaller,
move to the left child; otherwise, move to the right child.
Listing 3.29: Code example

1 // Example : Search in a binary search tree ( BST )


2

3 class TreeNode
4 {
5 public int Value ;
6 public TreeNode Left ;
7 public TreeNode Right ;
8

9 public TreeNode ( int value )


10 {
11 Value = value ;

127
3.2. Search Algorithms in .NET

12 Left = null ;
13 Right = null ;
14 }
15 }
16

17 bool SearchBST ( TreeNode node , int target )


18 {
19 if ( node == null ) return false ;
20 if ( node . Value == target ) return true ;
21

22 if ( target < node . Value )


23 return SearchBST ( node . Left , target ) ;
24 else
25 return SearchBST ( node . Right , target ) ;
26 }
27

28 TreeNode root = new TreeNode (50) ;


29 root . Left = new TreeNode (30) ;
30 root . Right = new TreeNode (70) ;
31 Console . WriteLine ( SearchBST ( root , 70) ) ; // Output : true

How do you perform search in a balanced binary search tree (AVL or


Red-Black Tree) in .NET?
Searching in a balanced binary search tree (like AVL or Red-Black Tree) follows the same process
as a normal BST. However, these trees ensure balanced height, which improves performance in
worst-case scenarios.

Listing 3.30: Code example

1 // Example : Search in an AVL tree ( similar to BST search logic )


2

3 bool SearchAVL ( TreeNode node , int target )


4 {
5 return SearchBST ( node , target ) ; // Same logic as a standard BST search
6 }

How do you implement search using a trie (prefix tree) in .NET?


A trie is a tree-like data structure where each node represents a single character. It is commonly
used for searching words or prefixes in a dictionary. Searching involves traversing the trie based
on the characters of the target word.

Listing 3.31: Code example

1 // Example : Trie search implementation

128
3.2. Search Algorithms in .NET

3 class TrieNode
4 {
5 public Dictionary < char , TrieNode > Children = new Dictionary < char , TrieNode >() ;
6 public bool IsEndOfWord = false ;
7 }
8

9 class Trie
10 {
11 private TrieNode root = new TrieNode () ;
12

13 public void Insert ( string word )


14 {
15 TrieNode node = root ;
16 foreach ( char c in word )
17 {
18 if (! node . Children . ContainsKey ( c ) )
19 node . Children [ c ] = new TrieNode () ;
20 node = node . Children [ c ];
21 }
22 node . IsEndOfWord = true ;
23 }
24

25 public bool Search ( string word )


26 {
27 TrieNode node = root ;
28 foreach ( char c in word )
29 {
30 if (! node . Children . ContainsKey ( c ) )
31 return false ;
32 node = node . Children [ c ];
33 }
34 return node . IsEndOfWord ;
35 }
36 }
37

38 Trie trie = new Trie () ;


39 trie . Insert ( " hello " ) ;
40 Console . WriteLine ( trie . Search ( " hello " ) ) ; // Output : true
41 Console . WriteLine ( trie . Search ( " world " ) ) ; // Output : false

How do you implement A* search in .NET?


A* search is an informed search algorithm used in pathfinding and graph traversal. It uses a
heuristic to estimate the shortest path to the goal. It combines the advantages of both Dijkstra’s
algorithm and best-first search.

129
3.2. Search Algorithms in .NET

Listing 3.32: Code example

1 // Example : A * search implementation


2

3 class AStarNode
4 {
5 public int X , Y ;
6 public int GCost , HCost ;
7 public AStarNode Parent ;
8

9 public int FCost = > GCost + HCost ;


10 }
11

12 AStarNode AStarSearch ( AStarNode start , AStarNode goal )


13 {
14 var openList = new List < AStarNode > { start };
15 var closedList = new HashSet < AStarNode >() ;
16

17 while ( openList . Count > 0)


18 {
19 var current = openList . OrderBy ( node = > node . FCost ) . First () ;
20

21 if ( current == goal )
22 return current ; // Path found
23

24 openList . Remove ( current ) ;


25 closedList . Add ( current ) ;
26

27 foreach ( var neighbor in GetNeighbors ( current ) )


28 {
29 if ( closedList . Contains ( neighbor ) ) continue ;
30

31 int tentativeGCost = current . GCost + GetDistance ( current , neighbor ) ;


32

33 if (! openList . Contains ( neighbor ) || tentativeGCost < neighbor . GCost )


34 {
35 neighbor . GCost = tentativeGCost ;
36 neighbor . HCost = GetDistance ( neighbor , goal ) ;
37 neighbor . Parent = current ;
38

39 if (! openList . Contains ( neighbor ) )


40 openList . Add ( neighbor ) ;
41 }
42 }
43 }
44 return null ; // No path found
45 }

130
3.2. Search Algorithms in .NET

How do you implement ternary search in .NET?


Ternary search is a divide-and-conquer algorithm similar to binary search. Instead of dividing the
array into two parts, it divides it into three and recursively checks one of the three parts.

Listing 3.33: Code example

1 // Example : Ternary search implementation


2

3 int TernarySearch ( int [] array , int left , int right , int target )
4 {
5 if ( right >= left )
6 {
7 int mid1 = left + ( right - left ) / 3;
8 int mid2 = right - ( right - left ) / 3;
9

10 if ( array [ mid1 ] == target ) return mid1 ;


11 if ( array [ mid2 ] == target ) return mid2 ;
12

13 if ( target < array [ mid1 ])


14 return TernarySearch ( array , left , mid1 - 1 , target ) ;
15 else if ( target > array [ mid2 ])
16 return TernarySearch ( array , mid2 + 1 , right , target ) ;
17 else
18 return TernarySearch ( array , mid1 + 1 , mid2 - 1 , target ) ;
19 }
20 return -1;
21 }
22

23 int [] arr = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 };
24 int result = TernarySearch ( arr , 0 , arr . Length - 1 , 5) ;
25 Console . WriteLine ( result ) ; // Output : 4

What is the difference between depth-first search (DFS) and breadth-first


search (BFS) in .NET?
DFS explores as far down a branch as possible before backtracking, while BFS explores all neighbors
at the current depth level before moving to the next level. DFS uses a stack (or recursion), and
BFS uses a queue.

Listing 3.34: Code example

1 // Example : Difference between DFS and BFS


2

3 // DFS uses recursion or a stack


4 void DFS ( Dictionary < int , List < int > > graph , int node , HashSet < int > visited )
5 {

131
3.2. Search Algorithms in .NET

6 visited . Add ( node ) ;


7 foreach ( var neighbor in graph [ node ])
8 {
9 if (! visited . Contains ( neighbor ) )
10 {
11 DFS ( graph , neighbor , visited ) ;
12 }
13 }
14 }
15

16 // BFS uses a queue


17 void BFS ( Dictionary < int , List < int > > graph , int start )
18 {
19 Queue < int > queue = new Queue < int >() ;
20 HashSet < int > visited = new HashSet < int > { start };
21

22 queue . Enqueue ( start ) ;


23 while ( queue . Count > 0)
24 {
25 int node = queue . Dequeue () ;
26 foreach ( var neighbor in graph [ node ])
27 {
28 if (! visited . Contains ( neighbor ) )
29 {
30 visited . Add ( neighbor ) ;
31 queue . Enqueue ( neighbor ) ;
32 }
33 }
34 }
35 }

How do you perform search in a sorted 2D matrix in .NET?


To search in a sorted 2D matrix, treat the matrix as a 1D array and perform binary search. Another
approach is to search from the top-right corner, moving left if the target is smaller and moving
down if the target is larger.
Listing 3.35: Code example

1 // Example : Search in a sorted 2 D matrix


2

3 bool SearchMatrix ( int [ ,] matrix , int target )


4 {
5 int rows = matrix . GetLength (0) ;
6 int cols = matrix . GetLength (1) ;
7 int row = 0 , col = cols - 1;
8

132
3.2. Search Algorithms in .NET

9 while ( row < rows && col >= 0)


10 {
11 if ( matrix [ row , col ] == target )
12 return true ;
13 else if ( matrix [ row , col ] > target )
14 col - -;
15 else
16 row ++;
17 }
18 return false ;
19 }
20

21 int [ ,] matrix = { { 1 , 3 , 5 , 7 } , { 10 , 11 , 16 , 20 } , { 23 , 30 , 34 , 60 } };
22 Console . WriteLine ( SearchMatrix ( matrix , 16) ) ; // Output : true

How do you perform KMP (Knuth-Morris-Pratt) string search in .NET?


The KMP algorithm efficiently searches for a pattern in a text by preprocessing the pattern to
create a longest prefix-suffix (LPS) array. It ensures that the text is not re-checked unnecessarily.

Listing 3.36: Code example

1 // Example : KMP algorithm implementation


2

3 void KMPSearch ( string pattern , string text )


4 {
5 int m = pattern . Length ;
6 int n = text . Length ;
7 int [] lps = new int [ m ];
8 int j = 0; // index for pattern
9

10 ComputeLPSArray ( pattern , m , lps ) ;


11

12 int i = 0; // index for text


13 while ( i < n )
14 {
15 if ( pattern [ j ] == text [ i ])
16 {
17 i ++;
18 j ++;
19 }
20

21 if ( j == m )
22 {
23 Console . WriteLine ( " Found pattern at index " + ( i - j ) ) ;
24 j = lps [ j - 1];
25 }

133
3.2. Search Algorithms in .NET

26 else if ( i < n && pattern [ j ] != text [ i ])


27 {
28 if ( j != 0)
29 j = lps [ j - 1];
30 else
31 i ++;
32 }
33 }
34 }
35

36 void ComputeLPSArray ( string pattern , int m , int [] lps )


37 {
38 int len = 0;
39 lps [0] = 0;
40 int i = 1;
41

42 while ( i < m )
43 {
44 if ( pattern [ i ] == pattern [ len ])
45 {
46 len ++;
47 lps [ i ] = len ;
48 i ++;
49 }
50 else
51 {
52 if ( len != 0)
53 {
54 len = lps [ len - 1];
55 }
56 else
57 {
58 lps [ i ] = 0;
59 i ++;
60 }
61 }
62 }
63 }
64

65 string text = " ababcabcabababd " ;


66 string pattern = " ababd " ;
67 KMPSearch ( pattern , text ) ; // Output : Found pattern at index 10

134
3.2. Search Algorithms in .NET

How do you perform ternary search tree (TST) search in .NET?


Ternary search tree (TST) is a data structure used for storing strings. It combines features of a
binary search tree and a trie. Each node contains three children and can store a character.

Listing 3.37: Code example

1 // Example : Ternary search tree ( TST ) implementation


2

3 class TSTNode
4 {
5 public char Character ;
6 public TSTNode Left , Middle , Right ;
7 public bool IsEndOfString ;
8

9 public TSTNode ( char character )


10 {
11 Character = character ;
12 }
13 }
14

15 class TernarySearchTree
16 {
17 public TSTNode Root ;
18

19 public void Insert ( string word )


20 {
21 Root = InsertRec ( Root , word , 0) ;
22 }
23

24 private TSTNode InsertRec ( TSTNode node , string word , int index )


25 {
26 char character = word [ index ];
27

28 if ( node == null ) node = new TSTNode ( character ) ;


29

30 if ( character < node . Character )


31 node . Left = InsertRec ( node . Left , word , index ) ;
32 else if ( character > node . Character )
33 node . Right = InsertRec ( node . Right , word , index ) ;
34 else if ( index + 1 < word . Length )
35 node . Middle = InsertRec ( node . Middle , word , index + 1) ;
36 else
37 node . IsEndOfString = true ;
38

39 return node ;
40 }
41

135
3.2. Search Algorithms in .NET

42 public bool Search ( string word )


43 {
44 return SearchRec ( Root , word , 0) ;
45 }
46

47 private bool SearchRec ( TSTNode node , string word , int index )


48 {
49 if ( node == null ) return false ;
50

51 char character = word [ index ];


52

53 if ( character < node . Character )


54 return SearchRec ( node . Left , word , index ) ;
55 else if ( character > node . Character )
56 return SearchRec ( node . Right , word , index ) ;
57 else if ( index + 1 == word . Length )
58 return node . IsEndOfString ;
59 else
60 return SearchRec ( node . Middle , word , index + 1) ;
61 }
62 }
63

64 TernarySearchTree tst = new TernarySearchTree () ;


65 tst . Insert ( " code " ) ;
66 Console . WriteLine ( tst . Search ( " code " ) ) ; // Output : true
67 Console . WriteLine ( tst . Search ( " coder " ) ) ; // Output : false

How do you implement Fibonacci search in .NET?


Fibonacci search is similar to binary search but uses Fibonacci numbers to divide the array into
subarrays. It is especially useful for large datasets with expensive comparisons.

Listing 3.38: Code example

1 // Example : Fibonacci search implementation


2

3 int FibonacciSearch ( int [] array , int target )


4 {
5 int fibM2 = 0;
6 int fibM1 = 1;
7 int fibM = fibM2 + fibM1 ;
8

9 int n = array . Length ;


10 while ( fibM < n )
11 {
12 fibM2 = fibM1 ;
13 fibM1 = fibM ;

136
3.2. Search Algorithms in .NET

14 fibM = fibM2 + fibM1 ;


15 }
16

17 int offset = -1;


18 while ( fibM > 1)
19 {
20 int i = Math . Min ( offset + fibM2 , n - 1) ;
21

22 if ( array [ i ] < target )


23 {
24 fibM = fibM1 ;
25 fibM1 = fibM2 ;
26 fibM2 = fibM - fibM1 ;
27 offset = i ;
28 }
29 else if ( array [ i ] > target )
30 {
31 fibM = fibM2 ;
32 fibM1 = fibM1 - fibM2 ;
33 fibM2 = fibM - fibM1 ;
34 }
35 else
36 return i ;
37 }
38

39 if ( fibM1 == 1 && array [ offset + 1] == target )


40 return offset + 1;
41

42 return -1;
43 }
44

45 int [] arr = { 10 , 22 , 35 , 40 , 45 , 50 , 80 , 82 , 85 , 90 , 100 };


46 int result = FibonacciSearch ( arr , 85) ;
47 Console . WriteLine ( result ) ; // Output : 8

How do you implement iterative deepening depth-first search (IDDFS)


in .NET?
IDDFS performs a depth-first search up to a given depth and increases the depth limit incremen-
tally. It combines the benefits of depth-first search’s space efficiency and breadth-first search’s
completeness.

Listing 3.39: Code example

1 // Example : IDDFS implementation


2

137
3.2. Search Algorithms in .NET

3 bool DLS ( Dictionary < int , List < int > > graph , int src , int target , int limit )
4 {
5 if ( src == target ) return true ;
6 if ( limit <= 0) return false ;
7

8 foreach ( var neighbor in graph [ src ])


9 {
10 if ( DLS ( graph , neighbor , target , limit - 1) )
11 return true ;
12 }
13 return false ;
14 }
15

16 bool IDDFS ( Dictionary < int , List < int > > graph , int src , int target , int maxDepth )
17 {
18 for ( int i = 0; i <= maxDepth ; i ++)
19 {
20 if ( DLS ( graph , src , target , i ) )
21 return true ;
22 }
23 return false ;
24 }
25

26 var graph = new Dictionary < int , List < int > >
27 {
28 { 0 , new List < int > { 1 , 2 } },
29 { 1 , new List < int > { 3 , 4 } },
30 { 2 , new List < int > { 5 , 6 } },
31 { 3 , new List < int >() } ,
32 { 4 , new List < int >() } ,
33 { 5 , new List < int >() } ,
34 { 6 , new List < int >() }
35 };
36

37 Console . WriteLine ( IDDFS ( graph , 0 , 6 , 3) ) ; // Output : true

How do you implement jump search in .NET?


Jump search is an improvement over linear search for sorted arrays. It jumps ahead by a fixed
number of steps and then performs linear search when it overshoots the target element.

Listing 3.40: Code example

1 // Example : Jump search implementation


2

3 int JumpSearch ( int [] array , int target )


4 {

138
3.2. Search Algorithms in .NET

5 int n = array . Length ;


6 int step = ( int ) Math . Floor ( Math . Sqrt ( n ) ) ;
7 int prev = 0;
8

9 while ( array [ Math . Min ( step , n ) - 1] < target )


10 {
11 prev = step ;
12 step += ( int ) Math . Floor ( Math . Sqrt ( n ) ) ;
13 if ( prev >= n )
14 return -1;
15 }
16

17 for ( int i = prev ; i < Math . Min ( step , n ) ; i ++)


18 {
19 if ( array [ i ] == target )
20 return i ;
21 }
22 return -1;
23 }
24

25 int [] arr = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 };
26 Console . WriteLine ( JumpSearch ( arr , 9) ) ; // Output : 9

How do you implement binary search in a circularly sorted array in .NET?


In a circularly sorted array, binary search can still be used by determining the pivot point where
the array was rotated and then adjusting the search space accordingly.

Listing 3.41: Code example

1 // Example : Binary search in a circularly sorted array


2

3 int CircularBinarySearch ( int [] array , int left , int right , int target )
4 {
5 if ( left > right ) return -1;
6

7 int mid = ( left + right ) / 2;


8

9 if ( array [ mid ] == target )


10 return mid ;
11

12 if ( array [ left ] <= array [ mid ])


13 {
14 if ( target >= array [ left ] && target <= array [ mid ])
15 return CircularBinarySearch ( array , left , mid - 1 , target ) ;
16 return CircularBinarySearch ( array , mid + 1 , right , target ) ;
17 }

139
3.2. Search Algorithms in .NET

18

19 if ( target >= array [ mid ] && target <= array [ right ])


20 return CircularBinarySearch ( array , mid + 1 , right , target ) ;
21 return CircularBinarySearch ( array , left , mid - 1 , target ) ;
22 }
23

24 int [] arr = { 12 , 14 , 18 , 21 , 3 , 6 , 9 };
25 Console . WriteLine ( CircularBinarySearch ( arr , 0 , arr . Length - 1 , 6) ) ; // Output : 5

How do you perform parallel binary search in .NET?


Parallel binary search divides the search space into multiple parts and searches for the target
element in parallel, speeding up the process on multicore systems.

Listing 3.42: Code example

1 // Example : Parallel binary search implementation


2

3 int ParallelBinarySearch ( int [] array , int target )


4 {
5 int numTasks = 4;
6 int partitionSize = array . Length / numTasks ;
7 var tasks = new Task < int >[ numTasks ];
8

9 for ( int i = 0; i < numTasks ; i ++)


10 {
11 int start = i * partitionSize ;
12 int end = ( i == numTasks - 1) ? array . Length : start + partitionSize ;
13

14 tasks [ i ] = Task . Run (() = > BinarySearch ( array , target , start , end ) ) ;
15 }
16

17 Task . WaitAll ( tasks ) ;


18

19 for ( int i = 0; i < numTasks ; i ++)


20 {
21 if ( tasks [ i ]. Result != -1)
22 return tasks [ i ]. Result ;
23 }
24 return -1;
25 }
26

27 int BinarySearch ( int [] array , int target , int left , int right )
28 {
29 while ( left < right )
30 {
31 int mid = ( left + right ) / 2;

140
3.3. Graph Algorithms in .NET

32 if ( array [ mid ] == target )


33 return mid ;
34 if ( array [ mid ] < target )
35 left = mid + 1;
36 else
37 right = mid ;
38 }
39 return -1;
40 }
41

42 int [] arr = { 2 , 3 , 4 , 10 , 40 };
43 Console . WriteLine ( ParallelBinarySearch ( arr , 10) ) ; // Output : 3

3.3 Graph Algorithms in .NET


How do you implement Dijkstra’s algorithm for shortest paths in a graph
in .NET?
Dijkstra’s algorithm finds the shortest path from a source node to all other nodes in a weighted
graph. It uses a priority queue to pick the node with the smallest tentative distance.

Listing 3.43: Code example

1 // Dijkstra ’s algorithm example


2 void Dijkstra ( Dictionary < int , List < Tuple < int , int > > > graph , int source )
3 {
4 // Stores shortest known distances from source to each node
5 var distances = new Dictionary < int , int >() ;
6

7 // Priority queue sorts by distance , stores nodes


8 var priorityQueue = new PriorityQueue < int , int >() ;
9

10 // Initialize all distances to infinity except the source


11 foreach ( var node in graph . Keys )
12 {
13 distances [ node ] = int . MaxValue ;
14 }
15 distances [ source ] = 0;
16

17 // Start with source node


18 priorityQueue . Enqueue ( source , 0) ;
19

20 while ( priorityQueue . Count > 0)


21 {
22 // Dequeue node with shortest tentative distance

141
3.3. Graph Algorithms in .NET

23 var currentNode = priorityQueue . Dequeue () ;


24

25 // Skip if the node has no neighbors


26 if (! graph . ContainsKey ( currentNode ) )
27 continue ;
28

29 // Check all adjacent nodes


30 foreach ( var neighbor in graph [ currentNode ])
31 {
32 var neighborNode = neighbor . Item1 ;
33 var weight = neighbor . Item2 ;
34

35 // Calculate new tentative distance


36 var newDistance = distances [ currentNode ] + weight ;
37

38 // Update if new distance is shorter


39 if ( newDistance < distances [ neighborNode ])
40 {
41 distances [ neighborNode ] = newDistance ;
42 priorityQueue . Enqueue ( neighborNode , newDistance ) ;
43 }
44 }
45 }
46

47 // Output the shortest distances


48 Console . WriteLine ( $ " Shortest distances from node { source }: " ) ;
49 foreach ( var distance in distances )
50 {
51 var distStr = distance . Value == int . MaxValue ? " infinity " : distance . Value
. ToString () ;
52 Console . WriteLine ( $ " Node { distance . Key }: { distStr } " ) ;
53 }
54 }

How do you implement Floyd-Warshall algorithm for all-pairs shortest


path in .NET?
The Floyd-Warshall algorithm is used to find the shortest paths between all pairs of vertices in a
graph.

Listing 3.44: Code example

1 // Floyd - Warshall algorithm implementation


2 void FloydWarshall ( int [ ,] graph )
3 {
4 int vertices = graph . GetLength (0) ;

142
3.3. Graph Algorithms in .NET

5 int [ ,] distances = new int [ vertices , vertices ];


6

7 // Initialize the distances matrix


8 for ( int i = 0; i < vertices ; i ++)
9 {
10 for ( int j = 0; j < vertices ; j ++)
11 {
12 distances [i , j ] = graph [i , j ];
13 }
14 }
15

16 // Main algorithm : updating shortest paths


17 for ( int k = 0; k < vertices ; k ++)
18 {
19 for ( int i = 0; i < vertices ; i ++)
20 {
21 for ( int j = 0; j < vertices ; j ++)
22 {
23 if ( distances [i , k ] != int . MaxValue && distances [k , j ] != int .
MaxValue &&
24 distances [i , k ] + distances [k , j ] < distances [i , j ])
25 {
26 distances [i , j ] = distances [i , k ] + distances [k , j ];
27 }
28 }
29 }
30 }
31

32 // Output the final shortest distances


33 Console . WriteLine ( " Shortest distances between all pairs of vertices : " ) ;
34 for ( int i = 0; i < vertices ; i ++)
35 {
36 for ( int j = 0; j < vertices ; j ++)
37 {
38 string distance = distances [i , j ] == int . MaxValue ? " infinity " :
distances [i , j ]. ToString () ;
39 Console . Write ( distance + " \ t " ) ;
40 }
41 Console . WriteLine () ;
42 }
43 }

How do you perform depth-first search (DFS) in a graph in .NET?


Depth-First Search (DFS) is a recursive algorithm that explores nodes and edges of a graph by
visiting one node and exploring its neighbors before moving on.

143
3.3. Graph Algorithms in .NET

Listing 3.45: Code example

1 // DFS implementation
2 void DFS ( Dictionary < int , List < int > > graph , int node , HashSet < int > visited )
3 {
4 // Mark the current node as visited
5 visited . Add ( node ) ;
6 Console . WriteLine ( $ " Visited node { node } " ) ;
7

8 // Recursively visit all unvisited neighbors


9 if ( graph . ContainsKey ( node ) )
10 {
11 foreach ( var neighbor in graph [ node ])
12 {
13 if (! visited . Contains ( neighbor ) )
14 {
15 DFS ( graph , neighbor , visited ) ;
16 }
17 }
18 }
19 }

How do you perform breadth-first search (BFS) in a graph in .NET?


Breadth-First Search (BFS) is an iterative algorithm that explores nodes level by level, starting
from the source node.
Listing 3.46: Code example

1 // BFS implementation
2 void BFS ( Dictionary < int , List < int > > graph , int startNode )
3 {
4 var visited = new HashSet < int >() ;
5 var queue = new Queue < int >() ;
6

7 visited . Add ( startNode ) ;


8 queue . Enqueue ( startNode ) ;
9

10 while ( queue . Count > 0)


11 {
12 var currentNode = queue . Dequeue () ;
13 Console . WriteLine ( $ " Visited node { currentNode } " ) ;
14

15 if ( graph . ContainsKey ( currentNode ) )


16 {
17 foreach ( var neighbor in graph [ currentNode ])
18 {
19 if (! visited . Contains ( neighbor ) )

144
3.3. Graph Algorithms in .NET

20 {
21 visited . Add ( neighbor ) ;
22 queue . Enqueue ( neighbor ) ;
23 }
24 }
25 }
26 }
27 }

How do you implement Prim’s algorithm for minimum spanning tree in


.NET?
Prim’s algorithm builds a minimum spanning tree by starting from an arbitrary vertex and adding
the smallest edge that connects the tree to a new vertex.

Listing 3.47: Code example

1 // Prim ’s algorithm implementation


2 void PrimsAlgorithm ( int [ ,] graph , int vertices )
3 {
4 var selected = new bool [ vertices ];
5 selected [0] = true ;
6 int edgeCount = 0;
7

8 Console . WriteLine ( " Edge : Weight " ) ;


9

10 while ( edgeCount < vertices - 1)


11 {
12 int min = int . MaxValue ;
13 int x = 0;
14 int y = 0;
15

16 for ( int i = 0; i < vertices ; i ++)


17 {
18 if ( selected [ i ])
19 {
20 for ( int j = 0; j < vertices ; j ++)
21 {
22 if (! selected [ j ] && graph [i , j ] != 0)
23 {
24 if ( min > graph [i , j ])
25 {
26 min = graph [i , j ];
27 x = i;
28 y = j;
29 }

145
3.3. Graph Algorithms in .NET

30 }
31 }
32 }
33 }
34 Console . WriteLine ( $ " { x } - { y } : { graph [x , y ]} " ) ;
35 selected [ y ] = true ;
36 edgeCount ++;
37 }
38 }

How do you implement Kruskal’s algorithm for minimum spanning tree


in .NET?
Kruskal’s algorithm sorts all edges of the graph by their weight and adds them to the spanning
tree if they don’t form a cycle.

Listing 3.48: Code example

1 // Edge class definition


2 class Edge
3 {
4 public int Source ;
5 public int Destination ;
6 public int Weight ;
7

8 public Edge ( int source , int destination , int weight )


9 {
10 Source = source ;
11 Destination = destination ;
12 Weight = weight ;
13 }
14 }
15

16 // Union - Find data structure for cycle detection


17 class UnionFind
18 {
19 private int [] parent ;
20

21 public UnionFind ( int size )


22 {
23 parent = new int [ size ];
24 for ( int i = 0; i < size ; i ++)
25 parent [ i ] = i ;
26 }
27

28 public int Find ( int node )

146
3.3. Graph Algorithms in .NET

29 {
30 if ( parent [ node ] != node )
31 parent [ node ] = Find ( parent [ node ]) ;
32 return parent [ node ];
33 }
34

35 public void Union ( int x , int y )


36 {
37 int xRoot = Find ( x ) ;
38 int yRoot = Find ( y ) ;
39 parent [ xRoot ] = yRoot ;
40 }
41 }
42

43 // Kruskal ’s algorithm implementation


44 void KruskalMST ( List < Edge > edges , int vertices )
45 {
46 edges = edges . OrderBy ( e = > e . Weight ) . ToList () ;
47 var uf = new UnionFind ( vertices ) ;
48

49 int edgeCount = 0;
50 int i = 0;
51

52 Console . WriteLine ( " Edge : Weight " ) ;


53

54 while ( edgeCount < vertices - 1 && i < edges . Count )


55 {
56 var edge = edges [ i ++];
57 int x = uf . Find ( edge . Source ) ;
58 int y = uf . Find ( edge . Destination ) ;
59

60 if ( x != y )
61 {
62 Console . WriteLine ( $ " { edge . Source } - { edge . Destination } : { edge . Weight }
");
63 uf . Union (x , y ) ;
64 edgeCount ++;
65 }
66 }
67 }

147
3.3. Graph Algorithms in .NET

How do you implement Bellman-Ford algorithm for shortest path in a


graph in .NET?
Bellman-Ford algorithm finds the shortest paths from a single source node to all nodes, even in
graphs with negative weights.

Listing 3.49: Code example

1 // Bellman - Ford algorithm implementation


2 void BellmanFord ( int [ ,] graph , int V , int src )
3 {
4 int [] dist = new int [ V ];
5

6 for ( int i = 0; i < V ; i ++)


7 dist [ i ] = int . MaxValue ;
8 dist [ src ] = 0;
9

10 for ( int i = 1; i <= V - 1; i ++)


11 {
12 for ( int u = 0; u < V ; u ++)
13 {
14 for ( int v = 0; v < V ; v ++)
15 {
16 if ( graph [u , v ] != 0 && dist [ u ] != int . MaxValue && dist [ u ] + graph
[u , v ] < dist [ v ])
17 {
18 dist [ v ] = dist [ u ] + graph [u , v ];
19 }
20 }
21 }
22 }
23

24 // Check for negative - weight cycles


25 for ( int u = 0; u < V ; u ++)
26 {
27 for ( int v = 0; v < V ; v ++)
28 {
29 if ( graph [u , v ] != 0 && dist [ u ] != int . MaxValue && dist [ u ] + graph [u ,
v ] < dist [ v ])
30 {
31 Console . WriteLine ( " Graph contains a negative - weight cycle " ) ;
32 return ;
33 }
34 }
35 }
36

37 Console . WriteLine ( " Vertex Distance from Source " ) ;

148
3.3. Graph Algorithms in .NET

38 for ( int i = 0; i < V ; i ++)


39 Console . WriteLine ( $ " { i }\ t \ t { dist [ i ]} " ) ;
40 }

How do you detect cycles in a directed graph using DFS in .NET?


Cycle detection in a directed graph can be done using DFS by checking if any node is visited twice
in the same path.

Listing 3.50: Code example

1 // Cycle detection using DFS


2 bool IsCyclicUtil ( Dictionary < int , List < int > > graph , int v , HashSet < int > visited ,
HashSet < int > recStack )
3 {
4 if (! visited . Contains ( v ) )
5 {
6 visited . Add ( v ) ;
7 recStack . Add ( v ) ;
8

9 if ( graph . ContainsKey ( v ) )
10 {
11 foreach ( var neighbor in graph [ v ])
12 {
13 if (! visited . Contains ( neighbor ) && IsCyclicUtil ( graph , neighbor ,
visited , recStack ) )
14 return true ;
15 else if ( recStack . Contains ( neighbor ) )
16 return true ;
17 }
18 }
19 }
20 recStack . Remove ( v ) ;
21 return false ;
22 }
23

24 bool IsCyclic ( Dictionary < int , List < int > > graph )
25 {
26 var visited = new HashSet < int >() ;
27 var recStack = new HashSet < int >() ;
28

29 foreach ( var node in graph . Keys )


30 {
31 if ( IsCyclicUtil ( graph , node , visited , recStack ) )
32 return true ;
33 }
34

149
3.3. Graph Algorithms in .NET

35 return false ;
36 }

How do you implement A* (A-star) algorithm in .NET?


A* algorithm is used to find the shortest path in a graph by combining the actual distance from
the start and the estimated distance to the goal (heuristic).

Listing 3.51: Code example

1 class Node
2 {
3 public string Name ;
4 public int GCost ;
5 public int HCost ;
6 public int FCost = > GCost + HCost ;
7 public Node Parent ;
8 public List <( Node , int ) > Neighbors ;
9

10 public Node ( string name )


11 {
12 Name = name ;
13 Neighbors = new List <( Node , int ) >() ;
14 }
15 }
16

17 // A * algorithm implementation
18 List < Node > AStar ( Node start , Node goal , List < Node > graph )
19 {
20 var openSet = new PriorityQueue < Node , int >() ;
21 var closedSet = new HashSet < Node >() ;
22

23 openSet . Enqueue ( start , start . FCost ) ;


24

25 while ( openSet . Count > 0)


26 {
27 var current = openSet . Dequeue () ;
28

29 if ( current == goal )
30 {
31 var path = new List < Node >() ;
32 while ( current != null )
33 {
34 path . Add ( current ) ;
35 current = current . Parent ;
36 }
37 path . Reverse () ;

150
3.3. Graph Algorithms in .NET

38 return path ;
39 }
40

41 closedSet . Add ( current ) ;


42

43 foreach ( var ( neighbor , cost ) in current . Neighbors )


44 {
45 if ( closedSet . Contains ( neighbor ) )
46 continue ;
47

48 int tentativeGCost = current . GCost + cost ;


49

50 if ( tentativeGCost < neighbor . GCost || ! openSet . UnorderedItems . Any ( i


= > i . Element == neighbor ) )
51 {
52 neighbor . GCost = tentativeGCost ;
53 neighbor . Parent = current ;
54

55 if (! openSet . UnorderedItems . Any ( i = > i . Element == neighbor ) )


56 openSet . Enqueue ( neighbor , neighbor . FCost ) ;
57 }
58 }
59 }
60

61 return null ; // No path found


62 }

How do you implement Tarjan’s algorithm for strongly connected com-


ponents (SCC) in .NET?
Tarjan’s algorithm finds all strongly connected components in a directed graph using DFS and
low-link values.

Listing 3.52: Code example

1 // Tarjan ’s SCC algorithm implementation


2 void SCCUtil ( Dictionary < int , List < int > > graph , int u , int [] disc , int [] low , Stack
< int > stack , bool [] stackMember , ref int time )
3 {
4 disc [ u ] = low [ u ] = ++ time ;
5 stack . Push ( u ) ;
6 stackMember [ u ] = true ;
7

8 if ( graph . ContainsKey ( u ) )
9 {
10 foreach ( var v in graph [ u ])

151
3.3. Graph Algorithms in .NET

11 {
12 if ( disc [ v ] == -1)
13 {
14 SCCUtil ( graph , v , disc , low , stack , stackMember , ref time ) ;
15 low [ u ] = Math . Min ( low [ u ] , low [ v ]) ;
16 }
17 else if ( stackMember [ v ])
18 {
19 low [ u ] = Math . Min ( low [ u ] , disc [ v ]) ;
20 }
21 }
22 }
23

24 if ( low [ u ] == disc [ u ])
25 {
26 Console . Write ( " SCC : " ) ;
27 int w ;
28 do
29 {
30 w = stack . Pop () ;
31 Console . Write ( w + " " ) ;
32 stackMember [ w ] = false ;
33 } while ( w != u ) ;
34 Console . WriteLine () ;
35 }
36 }
37

38 void TarjanSCC ( Dictionary < int , List < int > > graph , int vertices )
39 {
40 int [] disc = new int [ vertices ];
41 int [] low = new int [ vertices ];
42 bool [] stackMember = new bool [ vertices ];
43 Stack < int > stack = new Stack < int >() ;
44

45 for ( int i = 0; i < vertices ; i ++)


46 {
47 disc [ i ] = -1;
48 low [ i ] = -1;
49 stackMember [ i ] = false ;
50 }
51

52 int time = 0;
53 for ( int i = 0; i < vertices ; i ++)
54 {
55 if ( disc [ i ] == -1)
56 SCCUtil ( graph , i , disc , low , stack , stackMember , ref time ) ;
57 }

152
3.3. Graph Algorithms in .NET

58 }

How do you implement Kosaraju’s algorithm for strongly connected com-


ponents (SCC) in .NET?
Kosaraju’s algorithm is a two-pass DFS algorithm that identifies SCCs by processing nodes in
decreasing order of finish times.

Listing 3.53: Code example

1 // Kosaraju ’s algorithm implementation


2 void FillOrder ( int v , Dictionary < int , List < int > > graph , bool [] visited , Stack < int >
stack )
3 {
4 visited [ v ] = true ;
5

6 if ( graph . ContainsKey ( v ) )
7 {
8 foreach ( var neighbor in graph [ v ])
9 {
10 if (! visited [ neighbor ])
11 FillOrder ( neighbor , graph , visited , stack ) ;
12 }
13 }
14

15 stack . Push ( v ) ;
16 }
17

18 Dictionary < int , List < int > > GetTranspose ( Dictionary < int , List < int > > graph , int
vertices )
19 {
20 var transpose = new Dictionary < int , List < int > >() ;
21

22 foreach ( var node in graph . Keys )


23 {
24 foreach ( var neighbor in graph [ node ])
25 {
26 if (! transpose . ContainsKey ( neighbor ) )
27 transpose [ neighbor ] = new List < int >() ;
28

29 transpose [ neighbor ]. Add ( node ) ;


30 }
31 }
32

33 return transpose ;
34 }

153
3.3. Graph Algorithms in .NET

35

36 void DFSUtil ( int v , Dictionary < int , List < int > > graph , bool [] visited )
37 {
38 visited [ v ] = true ;
39 Console . Write ( v + " " ) ;
40

41 if ( graph . ContainsKey ( v ) )
42 {
43 foreach ( var neighbor in graph [ v ])
44 {
45 if (! visited [ neighbor ])
46 DFSUtil ( neighbor , graph , visited ) ;
47 }
48 }
49 }
50

51 void KosarajuSCC ( Dictionary < int , List < int > > graph , int vertices )
52 {
53 var stack = new Stack < int >() ;
54 var visited = new bool [ vertices ];
55

56 for ( int i = 0; i < vertices ; i ++)


57 visited [ i ] = false ;
58

59 for ( int i = 0; i < vertices ; i ++)


60 {
61 if (! visited [ i ])
62 FillOrder (i , graph , visited , stack ) ;
63 }
64

65 var transpose = GetTranspose ( graph , vertices ) ;


66

67 for ( int i = 0; i < vertices ; i ++)


68 visited [ i ] = false ;
69

70 while ( stack . Count > 0)


71 {
72 int v = stack . Pop () ;
73

74 if (! visited [ v ])
75 {
76 Console . Write ( " SCC : " ) ;
77 DFSUtil (v , transpose , visited ) ;
78 Console . WriteLine () ;
79 }
80 }
81 }

154
3.3. Graph Algorithms in .NET

How do you implement Johnson’s algorithm for all-pairs shortest paths


in .NET?
Johnson’s algorithm finds all-pairs shortest paths by reweighting edges to ensure no negative cycles,
then running Dijkstra’s algorithm from each vertex.
Listing 3.54: Code example

1 // Johnson ’s algorithm implementation


2 void JohnsonAlgorithm ( int [ ,] graph , int vertices )
3 {
4 int [ ,] dist = new int [ vertices , vertices ];
5

6 for ( int i = 0; i < vertices ; i ++)


7 {
8 for ( int j = 0; j < vertices ; j ++)
9 dist [i , j ] = graph [i , j ];
10 }
11

12 for ( int k = 0; k < vertices ; k ++)


13 {
14 for ( int i = 0; i < vertices ; i ++)
15 {
16 for ( int j = 0; j < vertices ; j ++)
17 {
18 if ( dist [i , k ] != int . MaxValue && dist [k , j ] != int . MaxValue )
19 {
20 if ( dist [i , j ] > dist [i , k ] + dist [k , j ])
21 dist [i , j ] = dist [i , k ] + dist [k , j ];
22 }
23 }
24 }
25 }
26

27 Console . WriteLine ( " Shortest distances between every pair of vertices : " ) ;
28 for ( int i = 0; i < vertices ; i ++)
29 {
30 for ( int j = 0; j < vertices ; j ++)
31 {
32 if ( dist [i , j ] == int . MaxValue )
33 Console . Write ( " INF " ) ;
34 else
35 Console . Write ( dist [i , j ] + " " ) ;
36 }
37 Console . WriteLine () ;
38 }
39 }

155
3.3. Graph Algorithms in .NET

How do you detect a bipartite graph using BFS in .NET?


A graph is bipartite if its vertices can be divided into two disjoint sets where no two vertices within
the same set are adjacent. BFS can be used to color the graph and check for violations.

Listing 3.55: Code example

1 // Bipartite graph detection using BFS


2 bool IsBipartite ( Dictionary < int , List < int > > graph , int vertices )
3 {
4 int [] colors = new int [ vertices ];
5 Array . Fill ( colors , -1) ;
6

7 for ( int i = 0; i < vertices ; i ++)


8 {
9 if ( colors [ i ] == -1)
10 {
11 Queue < int > queue = new Queue < int >() ;
12 queue . Enqueue ( i ) ;
13 colors [ i ] = 0;
14

15 while ( queue . Count > 0)


16 {
17 int u = queue . Dequeue () ;
18

19 if ( graph . ContainsKey ( u ) )
20 {
21 foreach ( var v in graph [ u ])
22 {
23 if ( colors [ v ] == -1)
24 {
25 colors [ v ] = 1 - colors [ u ];
26 queue . Enqueue ( v ) ;
27 }
28 else if ( colors [ v ] == colors [ u ])
29 {
30 return false ;
31 }
32 }
33 }
34 }
35 }
36 }
37

38 return true ;
39 }

156
3.3. Graph Algorithms in .NET

How do you implement Edmonds-Karp algorithm for maximum flow in


a network in .NET?
Edmonds-Karp algorithm is an implementation of the Ford-Fulkerson method for computing the
maximum flow in a flow network using BFS to find augmenting paths.

Listing 3.56: Code example

1 // Edmonds - Karp algorithm implementation


2 void EdmondsKarp ( int [ ,] graph , int source , int sink )
3 {
4 int vertices = graph . GetLength (0) ;
5 int [ ,] residualGraph = new int [ vertices , vertices ];
6

7 for ( int u = 0; u < vertices ; u ++)


8 for ( int v = 0; v < vertices ; v ++)
9 residualGraph [u , v ] = graph [u , v ];
10

11 int [] parent = new int [ vertices ];


12 int maxFlow = 0;
13

14 while ( BFS ( residualGraph , source , sink , parent , vertices ) )


15 {
16 int pathFlow = int . MaxValue ;
17 for ( int v = sink ; v != source ; v = parent [ v ])
18 {
19 int u = parent [ v ];
20 pathFlow = Math . Min ( pathFlow , residualGraph [u , v ]) ;
21 }
22

23 for ( int v = sink ; v != source ; v = parent [ v ])


24 {
25 int u = parent [ v ];
26 residualGraph [u , v ] -= pathFlow ;
27 residualGraph [v , u ] += pathFlow ;
28 }
29

30 maxFlow += pathFlow ;
31 }
32

33 Console . WriteLine ( $ " Maximum Flow : { maxFlow } " ) ;


34 }
35

36 bool BFS ( int [ ,] residualGraph , int source , int sink , int [] parent , int vertices )
37 {
38 bool [] visited = new bool [ vertices ];
39 Queue < int > queue = new Queue < int >() ;

157
3.3. Graph Algorithms in .NET

40

41 queue . Enqueue ( source ) ;


42 visited [ source ] = true ;
43 parent [ source ] = -1;
44

45 while ( queue . Count > 0)


46 {
47 int u = queue . Dequeue () ;
48

49 for ( int v = 0; v < vertices ; v ++)


50 {
51 if (! visited [ v ] && residualGraph [u , v ] > 0)
52 {
53 queue . Enqueue ( v ) ;
54 parent [ v ] = u ;
55 visited [ v ] = true ;
56 }
57 }
58 }
59

60 return visited [ sink ];


61 }

How do you implement Ford-Fulkerson algorithm for maximum flow in


.NET?
Ford-Fulkerson method calculates the maximum flow in a flow network by incrementally finding
augmenting paths using DFS or BFS.

Listing 3.57: Code example

1 // Ford - Fulkerson algorithm implementation


2 int FordFulkerson ( int [ ,] graph , int source , int sink )
3 {
4 int vertices = graph . GetLength (0) ;
5 int [ ,] residualGraph = new int [ vertices , vertices ];
6

7 for ( int u = 0; u < vertices ; u ++)


8 for ( int v = 0; v < vertices ; v ++)
9 residualGraph [u , v ] = graph [u , v ];
10

11 int [] parent = new int [ vertices ];


12 int maxFlow = 0;
13

14 while ( BFS ( residualGraph , source , sink , parent , vertices ) )


15 {

158
3.3. Graph Algorithms in .NET

16 int pathFlow = int . MaxValue ;


17 for ( int v = sink ; v != source ; v = parent [ v ])
18 {
19 int u = parent [ v ];
20 pathFlow = Math . Min ( pathFlow , residualGraph [u , v ]) ;
21 }
22

23 for ( int v = sink ; v != source ; v = parent [ v ])


24 {
25 int u = parent [ v ];
26 residualGraph [u , v ] -= pathFlow ;
27 residualGraph [v , u ] += pathFlow ;
28 }
29

30 maxFlow += pathFlow ;
31 }
32

33 return maxFlow ;
34 }
35

36 bool BFS ( int [ ,] residualGraph , int source , int sink , int [] parent , int vertices )
37 {
38 bool [] visited = new bool [ vertices ];
39 Queue < int > queue = new Queue < int >() ;
40

41 queue . Enqueue ( source ) ;


42 visited [ source ] = true ;
43 parent [ source ] = -1;
44

45 while ( queue . Count > 0)


46 {
47 int u = queue . Dequeue () ;
48

49 for ( int v = 0; v < vertices ; v ++)


50 {
51 if (! visited [ v ] && residualGraph [u , v ] > 0)
52 {
53 queue . Enqueue ( v ) ;
54 parent [ v ] = u ;
55 visited [ v ] = true ;
56 }
57 }
58 }
59

60 return visited [ sink ];


61 }

159
3.3. Graph Algorithms in .NET

How do you detect bridges (cut edges) in a graph using DFS in .NET?
Bridges in a graph are edges that, when removed, increase the number of connected components.
They can be found using DFS by keeping track of discovery and low values.

Listing 3.58: Code example

1 // Bridge detection using DFS


2 void FindBridges ( Dictionary < int , List < int > > graph , int vertices )
3 {
4 int [] disc = new int [ vertices ];
5 int [] low = new int [ vertices ];
6 int time = 0;
7 bool [] visited = new bool [ vertices ];
8

9 for ( int i = 0; i < vertices ; i ++)


10 {
11 if (! visited [ i ])
12 BridgeUtil ( graph , i , visited , disc , low , -1 , ref time ) ;
13 }
14 }
15

16 void BridgeUtil ( Dictionary < int , List < int > > graph , int u , bool [] visited , int []
disc , int [] low , int parent , ref int time )
17 {
18 visited [ u ] = true ;
19 disc [ u ] = low [ u ] = ++ time ;
20

21 if ( graph . ContainsKey ( u ) )
22 {
23 foreach ( var v in graph [ u ])
24 {
25 if (! visited [ v ])
26 {
27 BridgeUtil ( graph , v , visited , disc , low , u , ref time ) ;
28 low [ u ] = Math . Min ( low [ u ] , low [ v ]) ;
29

30 if ( low [ v ] > disc [ u ])


31 Console . WriteLine ( $ " Bridge found between nodes { u } and { v } " ) ;
32 }
33 else if ( v != parent )
34 low [ u ] = Math . Min ( low [ u ] , disc [ v ]) ;
35 }
36 }
37 }

160
3.3. Graph Algorithms in .NET

How do you detect articulation points (cut vertices) in a graph using


DFS in .NET?
Articulation points are vertices that, when removed, increase the number of connected components.
These can be identified using DFS with low-link values.

Listing 3.59: Code example

1 // Articulation point detection using DFS


2 void ArticulationPoints ( Dictionary < int , List < int > > graph , int vertices )
3 {
4 int [] disc = new int [ vertices ];
5 int [] low = new int [ vertices ];
6 bool [] visited = new bool [ vertices ];
7 bool [] isArticulationPoint = new bool [ vertices ];
8 int time = 0;
9

10 for ( int i = 0; i < vertices ; i ++)


11 {
12 if (! visited [ i ])
13 ArticulationPointUtil ( graph , i , visited , disc , low , -1 , ref time ,
isArticulationPoint ) ;
14 }
15

16 Console . WriteLine ( " Articulation points : " ) ;


17 for ( int i = 0; i < vertices ; i ++)
18 {
19 if ( isArticulationPoint [ i ])
20 Console . WriteLine ( i ) ;
21 }
22 }
23

24 void ArticulationPointUtil ( Dictionary < int , List < int > > graph , int u , bool [] visited
, int [] disc , int [] low , int parent , ref int time , bool [] isArticulationPoint )
25 {
26 visited [ u ] = true ;
27 disc [ u ] = low [ u ] = ++ time ;
28 int children = 0;
29

30 if ( graph . ContainsKey ( u ) )
31 {
32 foreach ( var v in graph [ u ])
33 {
34 if (! visited [ v ])
35 {
36 children ++;

161
3.3. Graph Algorithms in .NET

37 ArticulationPointUtil ( graph , v , visited , disc , low , u , ref time ,


isArticulationPoint ) ;
38

39 low [ u ] = Math . Min ( low [ u ] , low [ v ]) ;


40

41 if ( parent != -1 && low [ v ] >= disc [ u ])


42 isArticulationPoint [ u ] = true ;
43 }
44 else if ( v != parent )
45 low [ u ] = Math . Min ( low [ u ] , disc [ v ]) ;
46 }
47

48 if ( parent == -1 && children > 1)


49 isArticulationPoint [ u ] = true ;
50 }
51 }

How do you implement Topological Sorting in a Directed Acyclic Graph


(DAG) in .NET?
Topological sorting of a DAG is a linear ordering of its vertices such that for every directed edge
UV, vertex U comes before V. This can be done using DFS or Kahn’s algorithm.

Listing 3.60: Code example

1 // Topological sorting using DFS


2 void TopologicalSort ( Dictionary < int , List < int > > graph , int vertices )
3 {
4 Stack < int > stack = new Stack < int >() ;
5 bool [] visited = new bool [ vertices ];
6

7 for ( int i = 0; i < vertices ; i ++)


8 visited [ i ] = false ;
9

10 for ( int i = 0; i < vertices ; i ++)


11 {
12 if (! visited [ i ])
13 TopologicalSortUtil ( graph , i , visited , stack ) ;
14 }
15

16 Console . WriteLine ( " Topological sorting order : " ) ;


17 while ( stack . Count > 0)
18 Console . WriteLine ( stack . Pop () ) ;
19 }
20

162
3.3. Graph Algorithms in .NET

21 void TopologicalSortUtil ( Dictionary < int , List < int > > graph , int v , bool [] visited ,
Stack < int > stack )
22 {
23 visited [ v ] = true ;
24

25 if ( graph . ContainsKey ( v ) )
26 {
27 foreach ( var neighbor in graph [ v ])
28 {
29 if (! visited [ neighbor ])
30 TopologicalSortUtil ( graph , neighbor , visited , stack ) ;
31 }
32 }
33

34 stack . Push ( v ) ;
35 }

How do you detect a Hamiltonian cycle in a graph in .NET?


A Hamiltonian cycle is a cycle that visits every vertex exactly once. This can be solved using
backtracking or dynamic programming approaches.

Listing 3.61: Code example

1 // Hamiltonian cycle detection using backtracking


2 bool HamiltonianCycle ( Dictionary < int , List < int > > graph , int vertices )
3 {
4 int [] path = new int [ vertices ];
5 Array . Fill ( path , -1) ;
6 path [0] = 0;
7

8 if (! HamiltonianCycleUtil ( graph , path , 1 , vertices ) )


9 {
10 Console . WriteLine ( " No Hamiltonian Cycle found " ) ;
11 return false ;
12 }
13

14 Console . WriteLine ( " Hamiltonian Cycle found : " ) ;


15 foreach ( int vertex in path )
16 Console . Write ( vertex + " " ) ;
17 Console . WriteLine ( path [0]) ;
18

19 return true ;
20 }
21

22 bool HamiltonianCycleUtil ( Dictionary < int , List < int > > graph , int [] path , int
position , int vertices )

163
3.3. Graph Algorithms in .NET

23 {
24 if ( position == vertices )
25 {
26 return graph [ path [ position - 1]]. Contains ( path [0]) ;
27 }
28

29 for ( int v = 1; v < vertices ; v ++)


30 {
31 if ( IsSafe (v , graph , path , position ) )
32 {
33 path [ position ] = v ;
34

35 if ( HamiltonianCycleUtil ( graph , path , position + 1 , vertices ) )


36 return true ;
37

38 path [ position ] = -1;


39 }
40 }
41

42 return false ;
43 }
44

45 bool IsSafe ( int v , Dictionary < int , List < int > > graph , int [] path , int position )
46 {
47 if (! graph [ path [ position - 1]]. Contains ( v ) )
48 return false ;
49

50 for ( int i = 0; i < position ; i ++)


51 if ( path [ i ] == v )
52 return false ;
53

54 return true ;
55 }

How do you implement Kahn’s algorithm for topological sorting in .NET?


Kahn’s algorithm is used for topological sorting of a DAG by removing nodes with zero in-degree
and iteratively reducing the graph.

Listing 3.62: Code example

1 // Kahn ’s algorithm implementation


2 void KahnsAlgorithm ( Dictionary < int , List < int > > graph , int vertices )
3 {
4 int [] inDegree = new int [ vertices ];
5 foreach ( var node in graph )
6 {

164
3.3. Graph Algorithms in .NET

7 foreach ( var neighbor in node . Value )


8 {
9 inDegree [ neighbor ]++;
10 }
11 }
12

13 Queue < int > queue = new Queue < int >() ;
14 for ( int i = 0; i < vertices ; i ++)
15 {
16 if ( inDegree [ i ] == 0)
17 queue . Enqueue ( i ) ;
18 }
19

20 List < int > topOrder = new List < int >() ;
21 int count = 0;
22

23 while ( queue . Count > 0)


24 {
25 int u = queue . Dequeue () ;
26 topOrder . Add ( u ) ;
27

28 if ( graph . ContainsKey ( u ) )
29 {
30 foreach ( var neighbor in graph [ u ])
31 {
32 if ( - - inDegree [ neighbor ] == 0)
33 queue . Enqueue ( neighbor ) ;
34 }
35 }
36

37 count ++;
38 }
39

40 if ( count != vertices )
41 {
42 Console . WriteLine ( " Graph has a cycle ; topological sort not possible . " ) ;
43 }
44 else
45 {
46 Console . WriteLine ( " Topological Sort ( Kahn ’s Algorithm ) : " ) ;
47 foreach ( var v in topOrder )
48 Console . Write ( v + " " ) ;
49 Console . WriteLine () ;
50 }
51 }

165
3.4. Dynamic Programming in .NET

3.4 Dynamic Programming in .NET


How do you implement the Fibonacci sequence using dynamic program-
ming in .NET?
The Fibonacci sequence can be efficiently calculated using dynamic programming by storing pre-
vious results to avoid redundant calculations.

Listing 3.63: Code example

1 // Fibonacci using dynamic programming


2 int FibonacciDP ( int n )
3 {
4 int [] fib = new int [ n + 1];
5 fib [0] = 0;
6 fib [1] = 1;
7

8 for ( int i = 2; i <= n ; i ++)


9 {
10 fib [ i ] = fib [ i - 1] + fib [ i - 2];
11 }
12

13 return fib [ n ];
14 }

How do you implement the Longest Increasing Subsequence (LIS) prob-


lem in .NET using dynamic programming?
The LIS problem can be solved by maintaining an array to store the length of the longest subse-
quence ending at each element.

Listing 3.64: Code example

1 // Longest Increasing Subsequence using DP


2 int LongestIncreasingSubsequence ( int [] arr )
3 {
4 int n = arr . Length ;
5 int [] lis = new int [ n ];
6 for ( int i = 0; i < n ; i ++) lis [ i ] = 1;
7

8 for ( int i = 1; i < n ; i ++)


9 for ( int j = 0; j < i ; j ++)
10 if ( arr [ i ] > arr [ j ] && lis [ i ] < lis [ j ] + 1)
11 lis [ i ] = lis [ j ] + 1;
12

13 return lis . Max () ;

166
3.4. Dynamic Programming in .NET

14 }

How do you implement the 0/1 Knapsack problem using dynamic pro-
gramming in .NET?
The 0/1 Knapsack problem can be solved using dynamic programming by building a table where
each entry represents the maximum value that can be obtained with a given weight capacity.
Listing 3.65: Code example

1 // 0/1 Knapsack Problem using DP


2 int KnapsackDP ( int [] weights , int [] values , int capacity )
3 {
4 int n = weights . Length ;
5 int [ ,] dp = new int [ n + 1 , capacity + 1];
6

7 for ( int i = 1; i <= n ; i ++)


8 {
9 for ( int w = 0; w <= capacity ; w ++)
10 {
11 if ( weights [ i - 1] <= w )
12 dp [i , w ] = Math . Max ( dp [ i - 1 , w ] , dp [ i - 1 , w - weights [ i - 1]] +
values [ i - 1]) ;
13 else
14 dp [i , w ] = dp [ i - 1 , w ];
15 }
16 }
17

18 return dp [n , capacity ];
19 }

How do you solve the Coin Change problem using dynamic programming
in .NET?
The Coin Change problem can be solved by creating an array to store the minimum number of
coins required for each amount up to the target.
Listing 3.66: Code example

1 // Coin Change problem using DP


2 int CoinChange ( int [] coins , int amount )
3 {
4 int [] dp = new int [ amount + 1];
5 Array . Fill ( dp , amount + 1) ;
6 dp [0] = 0;
7

167
3.4. Dynamic Programming in .NET

8 for ( int i = 1; i <= amount ; i ++)


9 {
10 foreach ( int coin in coins )
11 {
12 if ( coin <= i )
13 {
14 dp [ i ] = Math . Min ( dp [ i ] , dp [ i - coin ] + 1) ;
15 }
16 }
17 }
18

19 return dp [ amount ] > amount ? -1 : dp [ amount ];


20 }

How do you solve the Longest Common Subsequence (LCS) problem


using dynamic programming in .NET?
The LCS problem can be solved by creating a 2D table where each entry represents the length of
the LCS for substrings of two given sequences.

Listing 3.67: Code example

1 // Longest Common Subsequence using DP


2 int LongestCommonSubsequence ( string s1 , string s2 )
3 {
4 int m = s1 . Length , n = s2 . Length ;
5 int [ ,] dp = new int [ m + 1 , n + 1];
6

7 for ( int i = 1; i <= m ; i ++)


8 {
9 for ( int j = 1; j <= n ; j ++)
10 {
11 if ( s1 [ i - 1] == s2 [ j - 1])
12 dp [i , j ] = dp [ i - 1 , j - 1] + 1;
13 else
14 dp [i , j ] = Math . Max ( dp [ i - 1 , j ] , dp [i , j - 1]) ;
15 }
16 }
17

18 return dp [m , n ];
19 }

168
3.4. Dynamic Programming in .NET

How do you solve the Edit Distance problem using dynamic programming
in .NET?
The Edit Distance problem, also known as the Levenshtein distance, can be solved by building a 2D
table where each entry represents the minimum number of operations to transform one substring
into another.

Listing 3.68: Code example

1 // Edit Distance using DP


2 int EditDistance ( string s1 , string s2 )
3 {
4 int m = s1 . Length , n = s2 . Length ;
5 int [ ,] dp = new int [ m + 1 , n + 1];
6

7 for ( int i = 0; i <= m ; i ++) dp [i , 0] = i ;


8 for ( int j = 0; j <= n ; j ++) dp [0 , j ] = j ;
9

10 for ( int i = 1; i <= m ; i ++)


11 {
12 for ( int j = 1; j <= n ; j ++)
13 {
14 if ( s1 [ i - 1] == s2 [ j - 1])
15 dp [i , j ] = dp [ i - 1 , j - 1];
16 else
17 dp [i , j ] = 1 + Math . Min ( dp [ i - 1 , j ] , Math . Min ( dp [i , j - 1] , dp [ i
- 1 , j - 1]) ) ;
18 }
19 }
20

21 return dp [m , n ];
22 }

How do you solve the Partition Equal Subset Sum problem using dynamic
programming in .NET?
The Partition Equal Subset Sum problem can be solved by checking whether the array can be
partitioned into two subsets with equal sum using a DP subset-sum approach.

Listing 3.69: Code example

1 // Partition Equal Subset Sum using DP


2 bool CanPartition ( int [] nums )
3 {
4 int sum = nums . Sum () ;
5 if ( sum % 2 != 0) return false ;

169
3.4. Dynamic Programming in .NET

6 int target = sum / 2;


7

8 bool [] dp = new bool [ target + 1];


9 dp [0] = true ;
10

11 foreach ( int num in nums )


12 {
13 for ( int i = target ; i >= num ; i - -)
14 {
15 dp [ i ] = dp [ i ] || dp [ i - num ];
16 }
17 }
18

19 return dp [ target ];
20 }

How do you implement the Minimum Path Sum problem using dynamic
programming in .NET?
Minimum Path Sum can be solved by using a 2D DP table where each entry contains the minimum
sum path to reach that cell from the top-left corner.
Listing 3.70: Code example

1 // Minimum Path Sum using DP


2 int MinPathSum ( int [ ,] grid )
3 {
4 int m = grid . GetLength (0) ;
5 int n = grid . GetLength (1) ;
6 int [ ,] dp = new int [m , n ];
7

8 dp [0 , 0] = grid [0 , 0];
9

10 for ( int i = 1; i < m ; i ++) dp [i , 0] = dp [ i - 1 , 0] + grid [i , 0];


11 for ( int j = 1; j < n ; j ++) dp [0 , j ] = dp [0 , j - 1] + grid [0 , j ];
12

13 for ( int i = 1; i < m ; i ++)


14 {
15 for ( int j = 1; j < n ; j ++)
16 {
17 dp [i , j ] = grid [i , j ] + Math . Min ( dp [ i - 1 , j ] , dp [i , j - 1]) ;
18 }
19 }
20

21 return dp [ m - 1 , n - 1];
22 }

170
3.4. Dynamic Programming in .NET

How do you solve the House Robber problem using dynamic program-
ming in .NET?
The House Robber problem can be solved using dynamic programming by keeping track of the
maximum amount of money that can be robbed up to each house without robbing two adjacent
houses.

Listing 3.71: Code example

1 // House Robber problem using DP


2 int Rob ( int [] nums )
3 {
4 if ( nums . Length == 0) return 0;
5 if ( nums . Length == 1) return nums [0];
6

7 int [] dp = new int [ nums . Length ];


8 dp [0] = nums [0];
9 dp [1] = Math . Max ( nums [0] , nums [1]) ;
10

11 for ( int i = 2; i < nums . Length ; i ++)


12 {
13 dp [ i ] = Math . Max ( dp [ i - 1] , dp [ i - 2] + nums [ i ]) ;
14 }
15

16 return dp [ nums . Length - 1];


17 }

How do you solve the Maximum Subarray problem using dynamic pro-
gramming in .NET?
The Maximum Subarray problem can be solved using dynamic programming by maintaining a
running sum and storing the maximum sum encountered.

Listing 3.72: Code example

1 // Maximum Subarray using DP


2 int MaxSubArray ( int [] nums )
3 {
4 int maxSoFar = nums [0];
5 int currentMax = nums [0];
6

7 for ( int i = 1; i < nums . Length ; i ++)


8 {
9 currentMax = Math . Max ( nums [ i ] , currentMax + nums [ i ]) ;
10 maxSoFar = Math . Max ( maxSoFar , currentMax ) ;
11 }

171
3.4. Dynamic Programming in .NET

12

13 return maxSoFar ;
14 }

How do you solve the Palindrome Partitioning problem using dynamic


programming in .NET?
The Palindrome Partitioning problem can be solved using dynamic programming by checking for
palindromes in substrings and storing the minimum cuts required.
Listing 3.73: Code example

1 // Palindrome Partitioning using DP


2 int MinCut ( string s )
3 {
4 int n = s . Length ;
5 int [] cut = new int [ n ];
6 bool [ ,] palin = new bool [n , n ];
7

8 for ( int i = 0; i < n ; i ++)


9 {
10 cut [ i ] = i ;
11 for ( int j = 0; j <= i ; j ++)
12 {
13 if ( s [ i ] == s [ j ] && ( i - j < 2 || palin [ j + 1 , i - 1]) )
14 {
15 palin [j , i ] = true ;
16 cut [ i ] = j == 0 ? 0 : Math . Min ( cut [ i ] , cut [ j - 1] + 1) ;
17 }
18 }
19 }
20

21 return cut [ n - 1];


22 }

How do you solve the Wildcard Matching problem using dynamic pro-
gramming in .NET?
The Wildcard Matching problem can be solved using dynamic programming by building a table
to check the match between string characters and wildcard characters.
Listing 3.74: Code example

1 // Wildcard Matching using DP


2 bool IsMatch ( string s , string p )
3 {

172
3.4. Dynamic Programming in .NET

4 int m = s . Length , n = p . Length ;


5 bool [ ,] dp = new bool [ m + 1 , n + 1];
6 dp [0 , 0] = true ;
7

8 for ( int j = 1; j <= n ; j ++)


9 if ( p [ j - 1] == ’* ’)
10 dp [0 , j ] = dp [0 , j - 1];
11

12 for ( int i = 1; i <= m ; i ++)


13 {
14 for ( int j = 1; j <= n ; j ++)
15 {
16 if ( p [ j - 1] == ’? ’ || p [ j - 1] == s [ i - 1])
17 dp [i , j ] = dp [ i - 1 , j - 1];
18 else if ( p [ j - 1] == ’* ’)
19 dp [i , j ] = dp [ i - 1 , j ] || dp [i , j - 1];
20 }
21 }
22

23 return dp [m , n ];
24 }

173
4 | 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.

4.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 4.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 {

174
4.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 4.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 }

175
4.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 4.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 4.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 )

176
4.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 4.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 }

177
4.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 4.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 4.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 }

178
4.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 4.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 4.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 . " ) ;

179
4.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 4.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 }

180
4.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 4.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 4.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 >() ;

181
4.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 4.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 4.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 }

182
4.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 4.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 4.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 }

183
4.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 4.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 4.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 4.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 } " ) ;

184
4.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 4.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

4.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 4.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 }

185
4.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 4.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.

186
4.2. Delegates and Parameters

Listing 4.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 4.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 }

187
4.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 4.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 4.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
*/ }

188
4.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 4.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 4.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 !

189
4.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 4.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 4.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.

190
4.2. Delegates and Parameters

Listing 4.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 4.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 !

191
4.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 4.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 4.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 }

192
4.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 4.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 4.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 )

193
4.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 4.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.

194
4.3. Value Types, Reference Types, Immutable, Semantics

Listing 4.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 }

4.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 4.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

195
4.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 4.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 4.41: Code example

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

6 class ImmutableClass
7 {

196
4.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 4.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 };

197
4.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 4.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 }

198
4.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 4.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 4.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

199
4.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 4.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.

200
4.3. Value Types, Reference Types, Immutable, Semantics

Listing 4.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 4.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.

201
4.3. Value Types, Reference Types, Immutable, Semantics

Listing 4.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 4.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 }

202
4.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 4.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 4.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 };

203
4.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 4.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 4.54: Code example

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

204
4.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 }

4.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 4.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 4.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

205
4.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 4.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 }

206
4.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 4.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 4.59: Code example

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

207
4.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 4.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 4.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 " ) ;

208
4.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 4.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 4.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 {

209
4.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 4.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 4.65: Code example

1 enum Days { Sunday , Monday , Tuesday }


2

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

210
4.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 4.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 4.67: Code example

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

211
4.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 4.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 }

212
4.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 4.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 4.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 }

213
4.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 4.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 4.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 }

214
4.5. Collections and LINQ

4.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 4.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 4.74: Code example

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

215
4.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 4.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 }

216
4.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 4.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 4.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 {

217
4.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 4.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 {

218
4.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 4.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.

219
4.5. Collections and LINQ

Listing 4.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 }

220
4.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 4.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 4.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 };

221
4.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 4.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 4.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

222
4.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 4.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 4.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

223
4.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 4.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 4.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 };

224
4.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 4.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 4.90: Code example

1 class Person

225
4.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 4.91: Code example

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

226
4.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 4.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 4.93: Code example

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

227
4.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 }

228
5 | 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.

5.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 5.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 ;

229
5.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 5.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 5.3: Code example

1 public class Animal


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

230
5.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 5.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 ;

231
5.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 5.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 5.6: Code example

1 public struct ValueTypeExample


2 {
3 public int X ;
4 }

232
5.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 5.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 }

233
5.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 5.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 }

234
5.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 5.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.

235
5.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 5.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

236
5.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 5.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.

237
5.1. Object-Oriented Programming (OOP)

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

Listing 5.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 5.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

238
5.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 5.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 )

239
5.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 5.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 5.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 " ) ;

240
5.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 5.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.

241
5.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 5.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 }

5.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 5.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.

242
5.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 5.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 5.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

243
5.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 5.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.

244
5.3. DRY (Don’t Repeat Yourself)

Listing 5.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 }

5.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.

245
5.4. Clean Code and Best Practices

Listing 5.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 }

5.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 5.25: Code example

1 class Invoice

246
5.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 5.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 }

247
5.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 5.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 5.28: Code example

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


unnecessary methods

248
5.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 5.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 ; }

249
5.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 5.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

250
5.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 5.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 ) ;

251
5.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 5.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 }

252
5.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 5.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 5.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 )

253
5.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 5.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 }

254
5.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 5.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 5.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

255
5.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 5.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 }

256
5.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 5.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 5.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 {

257
5.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 5.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 ;

258
5.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 5.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 5.43: Code example

1 // Tightly coupled code


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

259
5.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 }

5.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 5.44: Code example

1 // Example of a long method in legacy code

260
5.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 5.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

261
5.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 5.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 5.47: Code example

262
5.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 5.48: Code example

1 // Before refactoring : Magic numbers


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

263
5.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 5.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 }

264
5.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 5.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.

265
5.5. Incremental Refactor and Code Update Techniques

Listing 5.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 5.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 {

266
5.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 5.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 5.54: Code example

1 // Before refactoring : Reusing a temporary variable


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

267
5.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 5.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 }

268
5.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 5.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 5.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 }

269
5.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 5.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 5.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

270
5.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 5.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 5.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

271
5.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 5.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 }

272
6 | Design Patterns

Design patterns provide proven solutions to common software design problems and are widely
recognized as a key component of professional software development. They offer structured ap-
proaches to recurring challenges, promoting code that is reusable, flexible, and easier to maintain.

Understanding design patterns is especially important when applying for software engineering
roles, as interviewers often evaluate candidates on their ability to architect solutions using es-
tablished principles. Questions may involve identifying the appropriate pattern for a scenario or
discussing trade-offs between different design choices.

What Is Design Pattern?


A design pattern is a general reusable solution to a commonly occurring problem within a given
context in software design. It is not a finished solution that can be directly transformed into code
but a guideline or template on how to solve a particular problem that can be applied in various
contexts. Design patterns help developers avoid common pitfalls, increase code readability, and
improve system scalability.

What Are the Types of Design Patterns?


• Creational Patterns: Focus on object creation mechanisms, trying to create objects in a
manner suitable to the situation.
• Structural Patterns: Concerned with how classes and objects are composed to form larger
structures.
• Behavioral Patterns: Deal with object collaboration and how responsibilities are dele-
gated.

273
6.1. Creational Patterns

6.1 Creational Patterns


Singleton Pattern
Description: Ensures a class has only one instance and provides a global point of access to that
instance.
Problem Solved: Used when only one instance of a class should exist throughout the system,
such as for logging, configuration, or managing shared resources.
Mechanism: The Singleton pattern restricts object instantiation by using a private constructor
and maintaining a static reference to the single created instance. Access to this instance is provided
through a static method, which ensures that only one instance is created in a thread-safe manner
using locks.
Listing 6.1: Code example

1 public class Singleton


2 {
3 private static Singleton _instance ;
4 private static readonly object _lock = new object () ;
5

6 private Singleton () { }
7

8 public static Singleton Instance


9 {
10 get
11 {
12 lock ( _lock )
13 {
14 if ( _instance == null )
15 {
16 _instance = new Singleton () ;
17 }
18 return _instance ;
19 }
20 }
21 }
22 }
23

24 // Usage example
25 var singletonInstance = Singleton . Instance ;

Factory Method Pattern


Description: Defines an interface for creating an object but lets subclasses alter the type of
objects that will be created.

274
6.1. Creational Patterns

Problem Solved: Used when the exact type of an object cannot be determined until runtime,
or when the creation process varies by object.
Mechanism: The Factory Method pattern delegates the responsibility of creating objects to
subclasses, allowing them to decide which class to instantiate. This provides flexibility in object
creation, as subclasses can modify the object creation process without changing the client code.

Listing 6.2: Code example

1 // Step 1: Create a common interface or base class


2 public interface IVehicle
3 {
4 void Drive () ;
5 }
6

7 public class Car : IVehicle


8 {
9 public void Drive () = > Console . WriteLine ( " Driving a car " ) ;
10 }
11

12 public class Truck : IVehicle


13 {
14 public void Drive () = > Console . WriteLine ( " Driving a truck " ) ;
15 }
16

17 // Step 2: Factory Class to create objects


18 public class VehicleFactory
19 {
20 public static IVehicle CreateVehicle ( string type )
21 {
22 return type . ToLower () switch
23 {
24 " car " = > new Car () ,
25 " truck " = > new Truck () ,
26 _ = > throw new ArgumentException ( " Invalid vehicle type " )
27 };
28 }
29 }
30

31 // Usage
32 IVehicle vehicle = VehicleFactory . CreateVehicle ( " car " ) ;
33 vehicle . Drive () ; // Output : " Driving a car "

Abstract Factory Pattern


Description: Provides an interface for creating families of related or dependent objects without
specifying their concrete classes.

275
6.1. Creational Patterns

Problem Solved: Used when a system needs to create related objects, such as UI elements,
across different platforms without changing the code that uses them.
Mechanism: The Abstract Factory pattern defines a common interface for creating different
types of related objects. Concrete implementations of the factory provide actual instantiations
based on the platform or environment. This allows flexibility in object creation while maintaining
a clean separation of concerns.

Listing 6.3: Code example

1 // Step 1: Create abstract product interfaces


2 public interface IButton
3 {
4 void Render () ;
5 }
6

7 public interface ITextBox


8 {
9 void Render () ;
10 }
11

12 // Step 2: Concrete product implementations


13 public class ModernButton : IButton
14 {
15 public void Render () = > Console . WriteLine ( " Rendering a modern button " ) ;
16 }
17

18 public class ClassicButton : IButton


19 {
20 public void Render () = > Console . WriteLine ( " Rendering a classic button " ) ;
21 }
22

23 public class ModernTextBox : ITextBox


24 {
25 public void Render () = > Console . WriteLine ( " Rendering a modern textbox " ) ;
26 }
27

28 public class ClassicTextBox : ITextBox


29 {
30 public void Render () = > Console . WriteLine ( " Rendering a classic textbox " ) ;
31 }
32

33 // Step 3: Abstract factory interface and concrete factories


34 public interface IUIFactory
35 {
36 IButton CreateButton () ;
37 ITextBox CreateTextBox () ;
38 }

276
6.1. Creational Patterns

39

40 public class ModernUIFactory : IUIFactory


41 {
42 public IButton CreateButton () = > new ModernButton () ;
43 public ITextBox CreateTextBox () = > new ModernTextBox () ;
44 }
45

46 public class ClassicUIFactory : IUIFactory


47 {
48 public IButton CreateButton () = > new ClassicButton () ;
49 public ITextBox CreateTextBox () = > new ClassicTextBox () ;
50 }
51

52 // Usage
53 IUIFactory uiFactory = new ModernUIFactory () ;
54 IButton button = uiFactory . CreateButton () ;
55 ITextBox textBox = uiFactory . CreateTextBox () ;
56

57 button . Render () ; // Output : " Rendering a modern button "


58 textBox . Render () ; // Output : " Rendering a modern textbox "

Builder Pattern
Description: Separates the construction of a complex object from its representation, allowing the
same construction process to create different representations.
Problem Solved: Used when constructing a complex object step by step is required, especially
when different representations of the object are possible.
Mechanism: The Builder pattern breaks the object construction process into discrete steps,
with each step constructing a specific part of the object. A director may be used to manage the
construction process, ensuring that parts are built in a specific order while keeping the complexity
of object creation hidden from the client.
Listing 6.4: Code example

1 public class Product


2 {
3 public string PartA { get ; set ; }
4 public string PartB { get ; set ; }
5 }
6

7 public interface IBuilder


8 {
9 void BuildPartA () ;
10 void BuildPartB () ;
11 Product GetProduct () ;
12 }

277
6.1. Creational Patterns

13

14 public class ConcreteBuilder : IBuilder


15 {
16 private Product _product = new Product () ;
17

18 public void BuildPartA () = > _product . PartA = " PartA " ;


19 public void BuildPartB () = > _product . PartB = " PartB " ;
20 public Product GetProduct () = > _product ;
21 }
22

23 // Usage example
24 var builder = new ConcreteBuilder () ;
25 builder . BuildPartA () ;
26 builder . BuildPartB () ;
27 var product = builder . GetProduct () ;
28 Console . WriteLine ( $ " Product : { product . PartA } , { product . PartB } " ) ;

Prototype Pattern
Description: Creates new objects by copying an existing object, known as a prototype, rather
than creating new instances from scratch.
Problem Solved: Used when the cost of creating a new object is more expensive than copying
an existing instance.
Mechanism: The Prototype pattern involves creating an object by copying an existing pro-
totype object. The prototype object provides a ‘Clone‘ method that is responsible for creating a
duplicate, either through shallow or deep copying.

Listing 6.5: Code example

1 public class Prototype


2 {
3 public string Data { get ; set ; }
4

5 public Prototype Clone ()


6 {
7 return ( Prototype ) this . MemberwiseClone () ;
8 }
9 }
10

11 // Usage example
12 Prototype prototype = new Prototype { Data = " Original " };
13 Prototype clone = prototype . Clone () ;
14 Console . WriteLine ( clone . Data ) ;

278
6.2. Structural Patterns

6.2 Structural Patterns


Repository Pattern
Description: The Repository Pattern centralizes data access logic, providing a clean API for the
business layer to interact with data sources (like databases, APIs, or in-memory collections). It
abstracts the details of data persistence and retrieval, offering a consistent way to handle data
across the application.
Problem Solved: The Repository Pattern addresses the problem of tightly coupling business
logic with data access logic. By abstracting the persistence layer, it allows the business logic to
work with high-level abstractions rather than directly manipulating the data source. This promotes
testability, maintainability, and separation of concerns.
Mechanism: The Repository acts as a mediator between the domain and the data layer.
It encapsulates CRUD operations (Create, Read, Update, Delete) and other data-access logic,
exposing these operations through a consistent interface. The business layer interacts only with
the repository, without concern for the underlying data source or implementation.

Listing 6.6: Code example

1 using System ;
2 using System . Collections . Generic ;
3 using System . Linq ;
4 using Microsoft . EntityFrameworkCore ;
5

6 // Domain model
7 public class Product
8 {
9 public int Id { get ; set ; }
10 public string Name { get ; set ; }
11 public decimal Price { get ; set ; }
12 }
13

14 // Repository interface
15 public interface IRepository <T > where T : class
16 {
17 IEnumerable <T > GetAll () ;
18 T GetById ( int id ) ;
19 void Add ( T entity ) ;
20 void Update ( T entity ) ;
21 void Delete ( int id ) ;
22 }
23

24 // Repository implementation
25 public class Repository <T > : IRepository <T > where T : class
26 {

279
6.2. Structural Patterns

27 private readonly DbContext _context ;


28 private readonly DbSet <T > _dbSet ;
29

30 public Repository ( DbContext context )


31 {
32 _context = context ;
33 _dbSet = context . Set <T >() ;
34 }
35

36 public IEnumerable <T > GetAll ()


37 {
38 return _dbSet . ToList () ;
39 }
40

41 public T GetById ( int id )


42 {
43 return _dbSet . Find ( id ) ;
44 }
45

46 public void Add ( T entity )


47 {
48 _dbSet . Add ( entity ) ;
49 _context . SaveChanges () ;
50 }
51

52 public void Update ( T entity )


53 {
54 _dbSet . Update ( entity ) ;
55 _context . SaveChanges () ;
56 }
57

58 public void Delete ( int id )


59 {
60 var entity = _dbSet . Find ( id ) ;
61 if ( entity != null )
62 {
63 _dbSet . Remove ( entity ) ;
64 _context . SaveChanges () ;
65 }
66 }
67 }
68

69 // Example usage
70 public class Program
71 {
72 public static void Main ()
73 {

280
6.2. Structural Patterns

74 using ( var context = new DbContext ( options = > options . UseSqlServer ( "
YourConnectionString " ) ) )
75 {
76 IRepository < Product > productRepository = new Repository < Product >(
context ) ;
77

78 // Add a new product


79 productRepository . Add ( new Product { Name = " Laptop " , Price = 1200.00 m
}) ;
80

81 // Retrieve all products


82 var products = productRepository . GetAll () ;
83

84 foreach ( var product in products )


85 {
86 Console . WriteLine ( $ " { product . Id }: { product . Name } - { product . Price :
C}");
87 }
88 }
89 }
90 }

Adapter Pattern
Description: Converts the interface of a class into another interface the client expects. Adapter
lets classes work together that couldn’t otherwise because of incompatible interfaces.
Problem Solved: Used when you want to reuse a class that doesn’t fit the required interface
or system.
Mechanism: The Adapter pattern works by creating a wrapper class (the adapter) around
the existing class (adaptee) to translate its interface into a compatible one. This allows classes
with incompatible interfaces to work together without modifying their code.

Listing 6.7: Code example

1 public interface ITarget


2 {
3 string GetRequest () ;
4 }
5

6 public class Adaptee


7 {
8 public string GetSpecificRequest ()
9 {
10 return " Specific request " ;
11 }
12 }

281
6.2. Structural Patterns

13

14 public class Adapter : ITarget


15 {
16 private readonly Adaptee _adaptee ;
17

18 public Adapter ( Adaptee adaptee )


19 {
20 _adaptee = adaptee ;
21 }
22

23 public string GetRequest ()


24 {
25 return _adaptee . GetSpecificRequest () ;
26 }
27 }
28

29 // Usage example
30 Adaptee adaptee = new Adaptee () ;
31 ITarget target = new Adapter ( adaptee ) ;
32 Console . WriteLine ( target . GetRequest () ) ;

Bridge Pattern
Description: Decouples an abstraction from its implementation so that the two can vary inde-
pendently.
Problem Solved: Used when you want to separate the abstraction from its implementation,
especially in cases where both may evolve independently.
Mechanism: The Bridge pattern decouples an abstraction (e.g., remote control) from its
implementation (e.g., TV, Radio) by using composition. The abstraction holds a reference to an
implementation interface, allowing both to vary independently.

Listing 6.8: Code example

1 public interface IDevice


2 {
3 bool IsEnabled () ;
4 void Enable () ;
5 void Disable () ;
6 int GetVolume () ;
7 void SetVolume ( int percent ) ;
8 }
9

10 public class RemoteControl


11 {
12 protected IDevice _device ;
13

282
6.2. Structural Patterns

14 public RemoteControl ( IDevice device )


15 {
16 _device = device ;
17 }
18

19 public void TogglePower ()


20 {
21 if ( _device . IsEnabled () )
22 {
23 _device . Disable () ;
24 }
25 else
26 {
27 _device . Enable () ;
28 }
29 }
30

31 public void VolumeDown ()


32 {
33 _device . SetVolume ( _device . GetVolume () - 10) ;
34 }
35

36 public void VolumeUp ()


37 {
38 _device . SetVolume ( _device . GetVolume () + 10) ;
39 }
40 }
41

42 public class Tv : IDevice


43 {
44 private bool _enabled = false ;
45 private int _volume = 50;
46

47 public bool IsEnabled () = > _enabled ;


48 public void Enable () = > _enabled = true ;
49 public void Disable () = > _enabled = false ;
50 public int GetVolume () = > _volume ;
51 public void SetVolume ( int percent ) = > _volume = percent ;
52 }
53

54 var tv = new Tv () ;
55 var remote = new RemoteControl ( tv ) ;
56 remote . TogglePower () ;
57 remote . VolumeUp () ;

283
6.2. Structural Patterns

Composite Pattern
Description: Composes objects into tree structures to represent part-whole hierarchies. Com-
posite lets clients treat individual objects and compositions of objects uniformly.
Problem Solved: Used when you need to work with complex tree-like structures, and want
to treat individual objects and groups of objects the same way.
Mechanism: The Composite pattern organizes objects into a tree structure, where individual
objects and groups of objects implement the same interface. This allows both leaf and composite
objects to be treated uniformly in recursive structures like file directories or graphical objects.

Listing 6.9: Code example

1 public interface IComponent


2 {
3 void Operation () ;
4 }
5

6 public class Leaf : IComponent


7 {
8 public void Operation () = > Console . WriteLine ( " Leaf " ) ;
9 }
10

11 public class Composite : IComponent


12 {
13 private List < IComponent > _children = new List < IComponent >() ;
14

15 public void Add ( IComponent component )


16 {
17 _children . Add ( component ) ;
18 }
19

20 public void Operation ()


21 {
22 Console . WriteLine ( " Composite operation " ) ;
23 foreach ( var child in _children )
24 {
25 child . Operation () ;
26 }
27 }
28 }
29

30 var leaf = new Leaf () ;


31 var composite = new Composite () ;
32 composite . Add ( leaf ) ;
33 composite . Operation () ;

284
6.2. Structural Patterns

Decorator Pattern
Description: Dynamically adds responsibilities to objects.
Problem Solved: Used when you need to extend the functionality of objects in a flexible and
reusable way without modifying the class.
Mechanism: The Decorator pattern works by wrapping an existing object inside another
object (the decorator). The decorator implements the same interface as the original object and
forwards method calls to it, adding new behavior before or after the original method is invoked.

Listing 6.10: Code example

1 public interface IComponent


2 {
3 string Operation () ;
4 }
5

6 public class ConcreteComponent : IComponent


7 {
8 public string Operation ()
9 {
10 return " ConcreteComponent " ;
11 }
12 }
13

14 public class Decorator : IComponent


15 {
16 protected IComponent _component ;
17

18 public Decorator ( IComponent component )


19 {
20 _component = component ;
21 }
22

23 public virtual string Operation ()


24 {
25 return _component . Operation () ;
26 }
27 }
28

29 public class ConcreteDecoratorA : Decorator


30 {
31 public ConcreteDecoratorA ( IComponent component ) : base ( component ) { }
32

33 public override string Operation ()


34 {
35 return $ " ConcreteDecoratorA ({ base . Operation () }) " ;
36 }

285
6.2. Structural Patterns

37 }
38

39 IComponent component = new ConcreteComponent () ;


40 component = new ConcreteDecoratorA ( component ) ;
41 Console . WriteLine ( component . Operation () ) ;

Façade Pattern
Description: Provides a simplified interface to a complex subsystem.
Problem Solved: Used when you need to provide a simple interface to a complex system,
such as APIs or libraries.
Mechanism: The Façade pattern creates a simple interface (facade) that wraps a complex
subsystem, hiding the complexity from the client. This provides a single point of interaction,
making it easier to use the system.

Listing 6.11: Code example

1 public class Subsystem1


2 {
3 public string Operation1 () = > " Subsystem1 : Ready ! " ;
4 public string OperationN () = > " Subsystem1 : Go ! " ;
5 }
6

7 public class Subsystem2


8 {
9 public string Operation1 () = > " Subsystem2 : Get ready ! " ;
10 public string OperationZ () = > " Subsystem2 : Fire ! " ;
11 }
12

13 public class Facade


14 {
15 protected Subsystem1 _subsystem1 ;
16 protected Subsystem2 _subsystem2 ;
17

18 public Facade ( Subsystem1 subsystem1 , Subsystem2 subsystem2 )


19 {
20 _subsystem1 = subsystem1 ;
21 _subsystem2 = subsystem2 ;
22 }
23

24 public void Operation ()


25 {
26 Console . WriteLine ( _subsystem1 . Operation1 () ) ;
27 Console . WriteLine ( _subsystem1 . OperationN () ) ;
28 Console . WriteLine ( _subsystem2 . Operation1 () ) ;
29 Console . WriteLine ( _subsystem2 . OperationZ () ) ;

286
6.2. Structural Patterns

30 }
31 }
32

33 var subsystem1 = new Subsystem1 () ;


34 var subsystem2 = new Subsystem2 () ;
35 var facade = new Facade ( subsystem1 , subsystem2 ) ;
36 facade . Operation () ;

Flyweight Pattern
Description: A fine-grained instance used for efficient sharing.
Problem Solved: Used when you need to minimize memory usage by sharing as much data
as possible with similar objects.
Mechanism: The Flyweight pattern shares common object data across multiple instances to
minimize memory usage. A factory is often used to manage and create shared objects, while the
intrinsic (shared) state is separated from the extrinsic (unique) state.

Listing 6.12: Code example

1 public class Flyweight


2 {
3 private string _intrinsicState ;
4

5 public Flyweight ( string intrinsicState )


6 {
7 _intrinsicState = intrinsicState ;
8 }
9

10 public void Operation ( string extrinsicState )


11 {
12 Console . WriteLine ( $ " Intrinsic : { _intrinsicState } , Extrinsic : {
extrinsicState } " ) ;
13 }
14 }
15

16 public class FlyweightFactory


17 {
18 private Dictionary < string , Flyweight > _flyweights = new Dictionary < string ,
Flyweight >() ;
19

20 public Flyweight GetFlyweight ( string key )


21 {
22 if (! _flyweights . ContainsKey ( key ) )
23 {
24 _flyweights [ key ] = new Flyweight ( key ) ;
25 }

287
6.2. Structural Patterns

26 return _flyweights [ key ];


27 }
28 }
29

30 FlyweightFactory factory = new FlyweightFactory () ;


31 Flyweight flyweight1 = factory . GetFlyweight ( " A " ) ;
32 flyweight1 . Operation ( " 1 " ) ;
33

34 Flyweight flyweight2 = factory . GetFlyweight ( " A " ) ;


35 flyweight2 . Operation ( " 2 " ) ;

Proxy Pattern
Description: An object representing another object.
Problem Solved: Used when you need to control access to an object, like controlling expensive
object instantiation, or securing the access to sensitive data.
Mechanism: The Proxy pattern creates a placeholder or surrogate object that controls access
to the real object. The proxy typically performs lazy initialization, access control, or logging before
forwarding requests to the actual object.

Listing 6.13: Code example

1 public interface ISubject


2 {
3 void Request () ;
4 }
5

6 public class RealSubject : ISubject


7 {
8 public void Request ()
9 {
10 Console . WriteLine ( " RealSubject : Handling Request " ) ;
11 }
12 }
13

14 public class Proxy : ISubject


15 {
16 private RealSubject _realSubject ;
17

18 public void Request ()


19 {
20 if ( _realSubject == null )
21 {
22 _realSubject = new RealSubject () ;
23 }
24 _realSubject . Request () ;

288
6.3. Behavioral Patterns

25 }
26 }
27

28 ISubject proxy = new Proxy () ;


29 proxy . Request () ;

6.3 Behavioral Patterns


Chain of Responsibility Pattern
Description: A way of passing a request between a chain of objects. Each object in the chain
handles the request or passes it to the next object in the chain.
Problem Solved: Used when you need a decoupled way of passing requests through a chain
of handlers, where each handler can either handle the request or pass it along.
Mechanism: The Chain of Responsibility pattern organizes multiple handlers for a request
in a chain. Each handler decides whether to process the request or pass it to the next handler,
promoting decoupling and flexible request handling.

Listing 6.14: Code example

1 public abstract class Handler


2 {
3 private Handler _nextHandler ;
4

5 public Handler SetNext ( Handler handler )


6 {
7 _nextHandler = handler ;
8 return handler ;
9 }
10

11 public virtual object Handle ( object request )


12 {
13 if ( _nextHandler != null )
14 {
15 return _nextHandler . Handle ( request ) ;
16 }
17 else
18 {
19 return null ;
20 }
21 }
22 }
23

24 public class ConcreteHandlerA : Handler


25 {

289
6.3. Behavioral Patterns

26 public override object Handle ( object request )


27 {
28 if (( request as string ) == " A " )
29 {
30 return $ " Handled by ConcreteHandlerA " ;
31 }
32 else
33 {
34 return base . Handle ( request ) ;
35 }
36 }
37 }
38

39 var handlerA = new ConcreteHandlerA () ;


40 var handlerB = new ConcreteHandlerA () . SetNext ( handlerA ) ;
41 var result = handlerB . Handle ( " A " ) ;
42 Console . WriteLine ( result ) ;

Command Pattern
Description: Encapsulates a request as an object, thereby allowing for parameterization of clients
with queues, requests, and operations.
Problem Solved: Used when you need to issue requests, queue tasks, or execute commands
at different times, such as in undo/redo functionality.
Mechanism: The Command pattern encapsulates all details of a request (command) as an
object, decoupling the sender from the receiver. This allows commands to be executed later, stored,
or undone, making it useful for logging or undo systems.

Listing 6.15: Code example

1 public interface ICommand


2 {
3 void Execute () ;
4 }
5

6 public class Receiver


7 {
8 public void DoSomething ( string a )
9 {
10 Console . WriteLine ( $ " Receiver : Working on { a }. " ) ;
11 }
12 }
13

14 public class ConcreteCommand : ICommand


15 {
16 private Receiver _receiver ;

290
6.3. Behavioral Patterns

17 private string _a ;
18

19 public ConcreteCommand ( Receiver receiver , string a )


20 {
21 _receiver = receiver ;
22 _a = a ;
23 }
24

25 public void Execute ()


26 {
27 _receiver . DoSomething ( _a ) ;
28 }
29 }
30

31 Receiver receiver = new Receiver () ;


32 ICommand command = new ConcreteCommand ( receiver , " Task A " ) ;
33 command . Execute () ;

Mediator Pattern
Description: Defines simplified communication between classes.
Problem Solved: Used when you need to reduce dependencies between communicating classes
by centralizing all communication in a mediator object.
Mechanism: The Mediator pattern introduces a mediator object that handles communication
between multiple classes, reducing direct dependencies between them. This makes the system easier
to modify and extend by centralizing control logic.

Listing 6.16: Code example

1 public interface IMediator


2 {
3 void Notify ( object sender , string ev ) ;
4 }
5

6 public class ConcreteMediator : IMediator


7 {
8 private ConcreteComponent1 _component1 ;
9 private ConcreteComponent2 _component2 ;
10

11 public ConcreteMediator ( ConcreteComponent1 component1 , ConcreteComponent2


component2 )
12 {
13 _component1 = component1 ;
14 _component2 = component2 ;
15 _component1 . SetMediator ( this ) ;
16 _component2 . SetMediator ( this ) ;

291
6.3. Behavioral Patterns

17 }
18

19 public void Notify ( object sender , string ev )


20 {
21 if ( ev == " A " )
22 {
23 Console . WriteLine ( " Mediator reacts to A and triggers : " ) ;
24 _component2 . DoC () ;
25 }
26 if ( ev == " D " )
27 {
28 Console . WriteLine ( " Mediator reacts to D and triggers : " ) ;
29 _component1 . DoB () ;
30 }
31 }
32 }
33

34 public abstract class BaseComponent


35 {
36 protected IMediator _mediator ;
37

38 public BaseComponent ( IMediator mediator = null )


39 {
40 _mediator = mediator ;
41 }
42

43 public void SetMediator ( IMediator mediator )


44 {
45 _mediator = mediator ;
46 }
47 }
48

49 public class ConcreteComponent1 : BaseComponent


50 {
51 public void DoA ()
52 {
53 Console . WriteLine ( " Component 1 does A . " ) ;
54 _mediator . Notify ( this , " A " ) ;
55 }
56

57 public void DoB ()


58 {
59 Console . WriteLine ( " Component 1 does B . " ) ;
60 }
61 }
62

63 public class ConcreteComponent2 : BaseComponent

292
6.3. Behavioral Patterns

64 {
65 public void DoC ()
66 {
67 Console . WriteLine ( " Component 2 does C . " ) ;
68 _mediator . Notify ( this , " D " ) ;
69 }
70 }
71

72 ConcreteComponent1 component1 = new ConcreteComponent1 () ;


73 ConcreteComponent2 component2 = new ConcreteComponent2 () ;
74 new ConcreteMediator ( component1 , component2 ) ;
75

76 component1 . DoA () ;
77 component2 . DoC () ;

Memento Pattern
Description: Capture and restore an object’s internal state without violating encapsulation.
Problem Solved: Used when you need to save and restore the previous state of an object
while preserving encapsulation.
Mechanism: The Memento pattern stores the internal state of an object (memento) and
allows the object to return to this state later. A caretaker manages mementos without knowing
their content, preserving encapsulation.

Listing 6.17: Code example

1 public class Memento


2 {
3 public string State { get ; }
4

5 public Memento ( string state )


6 {
7 State = state ;
8 }
9 }
10

11 public class Originator


12 {
13 private string _state ;
14

15 public void SetState ( string state )


16 {
17 _state = state ;
18 Console . WriteLine ( $ " State set to { state } " ) ;
19 }
20

293
6.3. Behavioral Patterns

21 public Memento SaveStateToMemento ()


22 {
23 return new Memento ( _state ) ;
24 }
25

26 public void GetStateFromMemento ( Memento memento )


27 {
28 _state = memento . State ;
29 Console . WriteLine ( $ " State restored to { memento . State } " ) ;
30 }
31 }
32

33 public class Caretaker


34 {
35 private List < Memento > _mementoList = new List < Memento >() ;
36

37 public void Add ( Memento state )


38 {
39 _mementoList . Add ( state ) ;
40 }
41

42 public Memento Get ( int index )


43 {
44 return _mementoList [ index ];
45 }
46 }
47

48 Originator originator = new Originator () ;


49 Caretaker caretaker = new Caretaker () ;
50

51 originator . SetState ( " State1 " ) ;


52 caretaker . Add ( originator . SaveStateToMemento () ) ;
53

54 originator . SetState ( " State2 " ) ;


55 caretaker . Add ( originator . SaveStateToMemento () ) ;
56

57 originator . GetStateFromMemento ( caretaker . Get (0) ) ;

Observer Pattern
Description: Defines a one-to-many dependency between objects so that when one object changes
state, all its dependents are notified and updated automatically.
Problem Solved: Used when there is a need to notify multiple objects of changes to a
particular object’s state, like in event handling or data binding scenarios.
Mechanism: The Observer pattern involves a subject that maintains a list of observers. When

294
6.3. Behavioral Patterns

the subject changes, it notifies all observers. The observers can then update themselves based on
the new state.

Listing 6.18: Code example

1 public interface IObserver


2 {
3 void Update ( ISubject subject ) ;
4 }
5

6 public interface ISubject


7 {
8 void Attach ( IObserver observer ) ;
9 void Detach ( IObserver observer ) ;
10 void Notify () ;
11 }
12

13 public class Subject : ISubject


14 {
15 public int State { get ; set ; } = -0;
16 private List < IObserver > _observers = new List < IObserver >() ;
17

18 public void Attach ( IObserver observer ) = > _observers . Add ( observer ) ;


19 public void Detach ( IObserver observer ) = > _observers . Remove ( observer ) ;
20

21 public void Notify ()


22 {
23 foreach ( var observer in _observers )
24 {
25 observer . Update ( this ) ;
26 }
27 }
28

29 public void SomeBusinessLogic ()


30 {
31 State = new Random () . Next (0 , 10) ;
32 Notify () ;
33 }
34 }
35

36 public class ConcreteObserver : IObserver


37 {
38 public void Update ( ISubject subject )
39 {
40 if ( subject is Subject s )
41 {
42 Console . WriteLine ( $ " Observer : Reacted to the event . New State : { s .
State } " ) ;

295
6.3. Behavioral Patterns

43 }
44 }
45 }
46

47 var subject = new Subject () ;


48 var observer1 = new ConcreteObserver () ;
49 subject . Attach ( observer1 ) ;
50

51 subject . SomeBusinessLogic () ;

Strategy Pattern
Description: Defines a family of algorithms, encapsulates each one, and makes them interchange-
able. The strategy pattern lets the algorithm vary independently from the clients that use it.
Problem Solved: Used when you have different algorithms or behaviors that a client should
be able to switch between at runtime.
Mechanism: The Strategy pattern allows defining different algorithms (strategies) and making
them interchangeable within the same interface. The context class can change its strategy at
runtime without affecting the client code, promoting flexibility.

Listing 6.19: Code example

1 public interface IStrategy


2 {
3 string DoAlgorithm ( string data ) ;
4 }
5

6 public class ConcreteStrategyA : IStrategy


7 {
8 public string DoAlgorithm ( string data )
9 {
10 return $ " Strategy A : { data . ToUpper () } " ;
11 }
12 }
13

14 public class Context


15 {
16 private IStrategy _strategy ;
17

18 public Context ( IStrategy strategy )


19 {
20 _strategy = strategy ;
21 }
22

23 public void SetStrategy ( IStrategy strategy )


24 {

296
6.3. Behavioral Patterns

25 _strategy = strategy ;
26 }
27

28 public void DoSomeBusinessLogic ()


29 {
30 Console . WriteLine ( _strategy . DoAlgorithm ( " data " ) ) ;
31 }
32 }
33

34 Context context = new Context ( new ConcreteStrategyA () ) ;


35 context . DoSomeBusinessLogic () ;

Template Method Pattern


Description: Defines the skeleton of an algorithm in the superclass but lets subclasses override
specific steps of the algorithm without changing its structure.
Problem Solved: Used when you have a fixed structure of an algorithm but need to allow
subclasses to alter some steps in a flexible way.
Mechanism: The Template Method pattern defines the steps of an algorithm in a base class,
with some steps implemented by subclasses. This allows the algorithm to remain unchanged while
allowing specific steps to vary in the subclasses.

Listing 6.20: Code example

1 public abstract class AbstractClass


2 {
3 // The template method defines the skeleton of the algorithm
4 public void TemplateMethod ()
5 {
6 BaseOperation1 () ;
7 RequiredOperations1 () ;
8 BaseOperation2 () ;
9 Hook1 () ;
10 RequiredOperation2 () ;
11 BaseOperation3 () ;
12 Hook2 () ;
13 }
14

15 // These operations already have implementations .


16 protected void BaseOperation1 () = > Console . WriteLine ( " BaseOperation1 " ) ;
17 protected void BaseOperation2 () = > Console . WriteLine ( " BaseOperation2 " ) ;
18 protected void BaseOperation3 () = > Console . WriteLine ( " BaseOperation3 " ) ;
19

20 // These operations have to be implemented in subclasses .


21 protected abstract void RequiredOperations1 () ;
22 protected abstract void RequiredOperation2 () ;

297
6.3. Behavioral Patterns

23

24 // Hooks are " placeholders " operations


25 protected virtual void Hook1 () { }
26 protected virtual void Hook2 () { }
27 }
28

29 public class ConcreteClass : AbstractClass


30 {
31 protected override void RequiredOperations1 () = > Console . WriteLine ( "
ConcreteClass RequiredOperations1 " ) ;
32 protected override void RequiredOperation2 () = > Console . WriteLine ( "
ConcreteClass RequiredOperation2 " ) ;
33

34 protected override void Hook1 () = > Console . WriteLine ( " ConcreteClass Hook1 " ) ;
35 }
36

37 AbstractClass concreteClass = new ConcreteClass () ;


38 concreteClass . TemplateMethod () ;

Visitor Pattern
Description: Defines a new operation to a class without changing the class itself.
Problem Solved: Used when you need to add a new operation to a class without changing
the class’ internal structure.
Mechanism: The Visitor pattern allows defining new operations on a class hierarchy without
modifying the class itself. A visitor object is passed to each class, and it provides the new operation
logic. This promotes separation of concerns and extends functionality without changing the base
classes.

Listing 6.21: Code example

1 public interface IVisitor


2 {
3 void Visit ( ConcreteElementA element ) ;
4 void Visit ( ConcreteElementB element ) ;
5 }
6

7 public interface IElement


8 {
9 void Accept ( IVisitor visitor ) ;
10 }
11

12 public class ConcreteElementA : IElement


13 {
14 public void Accept ( IVisitor visitor )
15 {

298
6.3. Behavioral Patterns

16 visitor . Visit ( this ) ;


17 }
18

19 public string ExclusiveMethodOfConcreteElementA ()


20 {
21 return " A " ;
22 }
23 }
24

25 public class ConcreteElementB : IElement


26 {
27 public void Accept ( IVisitor visitor )
28 {
29 visitor . Visit ( this ) ;
30 }
31

32 public string ExclusiveMethodOfConcreteElementB ()


33 {
34 return " B " ;
35 }
36 }
37

38 public class ConcreteVisitor1 : IVisitor


39 {
40 public void Visit ( ConcreteElementA element )
41 {
42 Console . WriteLine ( $ " { element . ExclusiveMethodOfConcreteElementA () } +
ConcreteVisitor1 " ) ;
43 }
44

45 public void Visit ( ConcreteElementB element )


46 {
47 Console . WriteLine ( $ " { element . ExclusiveMethodOfConcreteElementB () } +
ConcreteVisitor1 " ) ;
48 }
49 }
50

51 var elements = new List < IElement > { new ConcreteElementA () , new ConcreteElementB ()
};
52 var visitor = new ConcreteVisitor1 () ;
53

54 foreach ( var element in elements )


55 {
56 element . Accept ( visitor ) ;
57 }

299
6.3. Behavioral Patterns

Dependency Injection Pattern


Description: Provides an external dependency to a class. It’s a specific form of Inversion of
Control where objects receive other objects they depend on.
Problem Solved: Used when you want to decouple object creation from the class that uses
it, allowing for easier testing, maintenance, and flexibility.
Mechanism: The Dependency Injection pattern provides objects that a class depends on
(services) from the outside rather than having the class create these dependencies itself. This
promotes loose coupling and easier testing by allowing dependencies to be mocked or substituted.

Listing 6.22: Code example

1 public interface IService


2 {
3 void Serve () ;
4 }
5

6 public class Service : IService


7 {
8 public void Serve () = > Console . WriteLine ( " Service Called " ) ;
9 }
10

11 public class Client


12 {
13 private IService _service ;
14

15 // Inject service via constructor


16 public Client ( IService service )
17 {
18 _service = service ;
19 }
20

21 public void Start ()


22 {
23 _service . Serve () ;
24 }
25 }
26

27 IService service = new Service () ;


28 Client client = new Client ( service ) ;
29 client . Start () ;

Interpreter Pattern
Description: Defines a way to interpret and evaluate language elements or expressions.

300
6.3. Behavioral Patterns

Problem Solved: Used when you need to interpret and execute a language or expressions like
mathematical expressions, grammar rules, or even simple commands.
Mechanism: The Interpreter pattern defines a grammar for the language, where each terminal
and non-terminal expression is a class. These classes define an ‘Interpret‘ method that processes
expressions and returns results. The pattern is commonly used for evaluating expressions in lan-
guage processing or command systems.

Listing 6.23: Code example

1 public interface IExpression


2 {
3 bool Interpret ( string context ) ;
4 }
5

6 public class TerminalExpression : IExpression


7 {
8 private string _data ;
9

10 public TerminalExpression ( string data )


11 {
12 _data = data ;
13 }
14

15 public bool Interpret ( string context )


16 {
17 return context . Contains ( _data ) ;
18 }
19 }
20

21 public class OrExpression : IExpression


22 {
23 private IExpression _expr1 ;
24 private IExpression _expr2 ;
25

26 public OrExpression ( IExpression expr1 , IExpression expr2 )


27 {
28 _expr1 = expr1 ;
29 _expr2 = expr2 ;
30 }
31

32 public bool Interpret ( string context )


33 {
34 return _expr1 . Interpret ( context ) || _expr2 . Interpret ( context ) ;
35 }
36 }
37

38 // Usage example

301
6.3. Behavioral Patterns

39 IExpression isJava = new TerminalExpression ( " Java " ) ;


40 IExpression isPython = new TerminalExpression ( " Python " ) ;
41

42 IExpression isJavaOrPython = new OrExpression ( isJava , isPython ) ;


43 Console . WriteLine ( isJavaOrPython . Interpret ( " Java " ) ) ; // Output : True
44 Console . WriteLine ( isJavaOrPython . Interpret ( " C ++ " ) ) ; // Output : False

Iterator Pattern
Description: Provides a way to sequentially access elements of a collection without exposing the
underlying representation.
Problem Solved: Used when you need to traverse a collection, such as a list, without exposing
its internal structure.
Mechanism: The Iterator pattern defines an interface for accessing elements of a collection se-
quentially, without exposing its internal structure. The pattern typically includes a ‘Next‘ method
to move through the collection and a ‘HasNext‘ method to check if the iteration is complete.

Listing 6.24: Code example

1 public interface IIterator


2 {
3 bool HasNext () ;
4 object Next () ;
5 }
6

7 public interface IAggregate


8 {
9 IIterator CreateIterator () ;
10 }
11

12 public class ConcreteIterator : IIterator


13 {
14 private List < string > _collection ;
15 private int _position = 0;
16

17 public ConcreteIterator ( List < string > collection )


18 {
19 _collection = collection ;
20 }
21

22 public bool HasNext ()


23 {
24 return _position < _collection . Count ;
25 }
26

27 public object Next ()

302
6.3. Behavioral Patterns

28 {
29 return _collection [ _position ++];
30 }
31 }
32

33 public class ConcreteAggregate : IAggregate


34 {
35 private List < string > _collection = new List < string >() ;
36

37 public void AddItem ( string item )


38 {
39 _collection . Add ( item ) ;
40 }
41

42 public IIterator CreateIterator ()


43 {
44 return new ConcreteIterator ( _collection ) ;
45 }
46 }
47

48 // Usage example
49 ConcreteAggregate aggregate = new ConcreteAggregate () ;
50 aggregate . AddItem ( " Item1 " ) ;
51 aggregate . AddItem ( " Item2 " ) ;
52

53 IIterator iterator = aggregate . CreateIterator () ;


54 while ( iterator . HasNext () )
55 {
56 Console . WriteLine ( iterator . Next () ) ;
57 }

State Pattern
Description: Allows an object to alter its behavior when its internal state changes.
Problem Solved: Used when an object needs to change its behavior based on its state, such
as a state machine.
Mechanism: The State pattern encapsulates state-based behavior into separate classes for
each state. The context (the object whose behavior depends on the state) holds a reference to
the current state and delegates actions to the state object. The state objects can transition the
context to different states.

Listing 6.25: Code example

1 public interface IState


2 {
3 void Handle ( Context context ) ;

303
6.3. Behavioral Patterns

4 }
5

6 public class ConcreteStateA : IState


7 {
8 public void Handle ( Context context )
9 {
10 Console . WriteLine ( " State A is handling request . " ) ;
11 context . State = new ConcreteStateB () ;
12 }
13 }
14

15 public class ConcreteStateB : IState


16 {
17 public void Handle ( Context context )
18 {
19 Console . WriteLine ( " State B is handling request . " ) ;
20 context . State = new ConcreteStateA () ;
21 }
22 }
23

24 public class Context


25 {
26 public IState State { get ; set ; }
27

28 public Context ( IState state )


29 {
30 State = state ;
31 }
32

33 public void Request ()


34 {
35 State . Handle ( this ) ;
36 }
37 }
38

39 // Usage example
40 Context context = new Context ( new ConcreteStateA () ) ;
41 context . Request () ; // Output : State A is handling request .
42 context . Request () ; // Output : State B is handling request .

Event Aggregator Pattern


Description: Centralizes the management of events, allowing various components to publish and
subscribe to events through a single event-handling mechanism.
Problem Solved: Useful when many components in a system need to communicate via events

304
6.3. Behavioral Patterns

without tightly coupling publishers and subscribers. This avoids complex event-handling logic
scattered throughout the system.
Mechanism: The Event Aggregator acts as a mediator that collects events from publishers
and dispatches them to the appropriate subscribers. It decouples the event sources from the event
consumers, centralizing event management in one place. Components can publish events to the
aggregator, and other components can subscribe to receive specific events without knowing about
the event sources.

Listing 6.26: Code example

1 public class EventAggregator


2 {
3 private readonly Dictionary < string , List < Action > > _subscribers = new
Dictionary < string , List < Action > >() ;
4

5 // Method for publishing an event


6 public void Publish ( string eventName )
7 {
8 if ( _subscribers . ContainsKey ( eventName ) )
9 {
10 foreach ( var subscriber in _subscribers [ eventName ])
11 {
12 subscriber . Invoke () ;
13 }
14 }
15 }
16

17 // Method for subscribing to an event


18 public void Subscribe ( string eventName , Action subscriberAction )
19 {
20 if (! _subscribers . ContainsKey ( eventName ) )
21 {
22 _subscribers [ eventName ] = new List < Action >() ;
23 }
24 _subscribers [ eventName ]. Add ( subscriberAction ) ;
25 }
26 }
27

28 // Usage example
29 public class Program
30 {
31 public static void Main ( string [] args )
32 {
33 var eventAggregator = new EventAggregator () ;
34

35 // Component 1 subscribes to the " OnButtonClick " event


36 eventAggregator . Subscribe ( " OnButtonClick " , () = > Console . WriteLine ( "

305
6.3. Behavioral Patterns

Component 1 responding to button click . " ) ) ;


37

38 // Component 2 subscribes to the same event


39 eventAggregator . Subscribe ( " OnButtonClick " , () = > Console . WriteLine ( "
Component 2 responding to button click . " ) ) ;
40

41 // When the button is clicked , publish the event


42 eventAggregator . Publish ( " OnButtonClick " ) ;
43

44 // Output :
45 // Component 1 responding to button click .
46 // Component 2 responding to button click .
47 }
48 }

306
7 | Memory Management and Per-
formance

Memory management and performance are critical aspects of .NET development that directly im-
pact application reliability and efficiency. Understanding how the .NET runtime handles memory
allocation, garbage collection, and resource cleanup enables developers to write applications that
are both responsive and resource-conscious.

In technical interviews, especially for mid-to-senior level positions, candidates are often ex-
pected to demonstrate awareness of performance considerations and memory usage. This might
include discussing how to avoid memory leaks, minimize allocations, or optimize code execution
in high-load scenarios.

7.1 Stack and Heap Memory


What is the difference between stack and heap memory in C#?
The stack is used for static memory allocation (like primitive types and reference variables), and
the heap is used for dynamic memory allocation (like objects and arrays). The stack is fast and
automatically managed (local variables are freed when out of scope), while the heap is slower and
requires garbage collection for memory management.

How does memory allocation differ for value types and reference types
in C#?
Value types (like ‘int‘, ‘struct‘) are stored directly in the stack, and reference types (like ‘class‘)
are stored on the heap, with a reference stored in the stack.

307
7.1. Stack and Heap Memory

Can you explain what a stack overflow is and give an example of how it
can occur?
Stack overflow occurs when the stack memory is exhausted, typically due to deep recursion or
unbounded function calls.

Listing 7.1: Code example

1 void RecursiveFunction ()
2 {
3 RecursiveFunction () ; // This will eventually cause a stack overflow
4 }
5 RecursiveFunction () ; // Unbounded recursion

What happens when you assign one reference type to another in terms
of memory?
Assigning one reference type to another copies the reference, not the actual object. Both variables
point to the same memory location on the heap.

Why is the stack memory considered faster than heap memory?


The stack is faster because memory allocation and deallocation follow a strict LIFO (Last In, First
Out) structure, making it predictable and efficient. Heap memory, on the other hand, requires
more complex allocation and garbage collection, which introduces overhead.

How is garbage collection related to heap memory?


Garbage collection is responsible for managing the heap by freeing up memory that is no longer
referenced by any object. It runs periodically to reclaim memory used by unreferenced objects.

How does the .NET runtime decide when to allocate memory on the
stack vs the heap?
Memory is allocated on the stack for local variables and value types (for short-lived objects), while
objects and reference types are allocated on the heap.

Can a value type be allocated on the heap in C#? If yes, how?


Yes, a value type can be allocated on the heap if it’s part of a reference type or boxed. Boxing a
value type moves it to the heap.

308
7.1. Stack and Heap Memory

Listing 7.2: Code example

1 int x = 5; // Allocated on the stack


2 object o = x ; // Boxing : ’x ’ is moved to the heap

Explain what memory leak is and how it occurs in managed languages


like C#.
In managed languages like C#, memory leaks can occur when objects are no longer used but are
still referenced by other objects, preventing the garbage collector from freeing their memory.

What is the maximum size of the stack in a typical C# application?


The maximum size of the stack depends on the environment. Typically, it is around 1 MB for each
thread in a C# application, but this can be configured.

Can static variables be stored on the stack?


No, static variables are stored in the heap, and they are shared across all instances of a class,
regardless of where the instances are created.

How does the .NET garbage collector handle objects that are no longer
in use?
The garbage collector in .NET follows a generation-based algorithm, where it collects objects that
are no longer referenced in memory. It prioritizes short-lived objects (Gen 0), followed by Gen 1,
and Gen 2 (long-lived objects).

What impact does allocating large objects have on heap memory in C#?
Large objects (typically greater than 85,000 bytes) are allocated on the Large Object Heap (LOH).
The LOH is collected less frequently, which can lead to memory fragmentation and performance
issues.

Can stack memory be resized dynamically?


No, the stack cannot be resized dynamically. Each thread has a fixed stack size, and if it overflows,
a stack overflow exception is thrown.

309
7.1. Stack and Heap Memory

What are the performance implications of heap fragmentation in C#?


Heap fragmentation occurs when memory blocks are allocated and deallocated frequently, leaving
small gaps between blocks. This leads to inefficient memory usage and may result in increased
garbage collection times and slower performance.

How does escape analysis work in C#, and how does it affect memory
allocation?
Escape analysis is a technique that determines whether an object can be allocated on the stack
instead of the heap by analyzing if the object escapes the scope of the method. In C#, this is
done by the JIT compiler to optimize memory allocation.

What is the role of the Large Object Heap (LOH) in C# memory man-
agement?
The Large Object Heap (LOH) is a special part of the heap reserved for objects larger than 85,000
bytes. The LOH is not compacted by the garbage collector, so it can suffer from fragmentation.

How can stack and heap memory issues lead to application crashes in
C#?
Stack memory issues (like stack overflow) and heap memory issues (like memory leaks or out-of-
memory exceptions) can lead to application crashes. Stack overflows occur when the stack limit is
exceeded, and heap issues occur when memory is not properly managed.

Why is it important to know the difference between stack and heap mem-
ory for performance optimization in C#?
Understanding the stack and heap helps in optimizing memory usage, reducing garbage collection
pressure, and improving performance by choosing the right data structures and object lifetimes.

Can heap memory be manually deallocated in C#?


No, in C#, memory is managed by the garbage collector, and manual deallocation is not allowed.
However, unmanaged resources (e.g., file handles) should be released using the ‘IDisposable‘ inter-
face and the ‘Dispose‘ method.

310
7.2. Garbage Collection

What is the impact of recursive function calls on the stack memory in


C#?
Each recursive call adds a new frame to the stack, which can quickly lead to a stack overflow if the
recursion depth is too large. It is important to ensure that recursive functions have a base case to
prevent infinite recursion.
Listing 7.3: Code example

1 int Factorial ( int n )


2 {
3 if ( n == 0) return 1; // Base case to prevent infinite recursion
4 return n * Factorial ( n - 1) ;
5 }

7.2 Garbage Collection


What are the different generations in the .NET garbage collector, and
why is this generational model important?
.NET uses a generational garbage collector with three generations: Gen 0, Gen 1, and Gen 2.
Objects are categorized based on their lifetime:
• Gen 0: Short-lived objects (like temporary variables) are collected frequently.
• Gen 1: Medium-lived objects.
• Gen 2: Long-lived objects (like static data).
The generational model optimizes performance by collecting short-lived objects frequently while
avoiding frequent collections of long-lived objects.

How does the garbage collector determine that an object is eligible for
collection in C#?
An object is eligible for garbage collection when there are no references to it in the application,
meaning it is unreachable. The garbage collector performs reachability analysis to identify such
objects and marks them for collection.

What is a memory leak in C#, and how can it occur even in a managed
environment?
A memory leak in C# occurs when objects that are no longer needed still have references, pre-
venting the garbage collector from freeing their memory. For example, static references or event
handlers that are not deregistered can cause memory leaks.

311
7.2. Garbage Collection

How does garbage collection impact performance in high-throughput ap-


plications?
Garbage collection can pause application threads, which is known as a "GC pause." In high-
throughput applications, frequent or long garbage collection pauses can degrade performance,
especially if many objects are being allocated and deallocated. Managing object lifetimes and
reducing allocations can help mitigate this.

What is the difference between a full garbage collection and an ephemeral


garbage collection?
An ephemeral collection targets **Gen 0** and **Gen 1** objects (short-lived objects) and is more
frequent. A full garbage collection, on the other hand, involves **Gen 2** (long-lived objects) and
the **Large Object Heap (LOH)**. Full collections are more costly because they scan more
memory and objects.

Can you explain how the Large Object Heap (LOH) is handled during
garbage collection?
Objects larger than 85,000 bytes are allocated on the **Large Object Heap (LOH)**. The LOH
is collected only during full (Gen 2) garbage collections, and it is not compacted by default, which
can lead to fragmentation.

What is the GC.Collect() method in C#, and when should it be used?


‘GC.Collect()‘ forces a garbage collection, but it should be used sparingly because it can degrade
performance by interrupting the normal GC cycle. It may be used in scenarios where you know
there are many unused objects, such as after loading a large dataset that’s no longer needed.

How can finalizers affect garbage collection, and what issues can arise
from their use?
Finalizers (‘ ClassName()‘) are methods that clean up unmanaged resources before the object is
collected. Objects with finalizers take longer to be collected because they must first go through
a finalization queue. This can delay the collection process, leading to more memory usage and
potential performance issues.

Listing 7.4: Code example

1 class MyClass
2 {

312
7.2. Garbage Collection

3 // Finalizer
4 ~ MyClass ()
5 {
6 // Clean up unmanaged resources
7 }
8 }

What are weak references, and how do they interact with garbage col-
lection in C#?
Weak references allow the garbage collector to collect an object even if it is still referenced, pre-
venting the object from keeping memory alive unnecessarily. They are useful when you want to
hold a reference to an object without preventing its collection.
Listing 7.5: Code example

1 WeakReference weakRef = new WeakReference ( someObject ) ;


2 if ( weakRef . IsAlive )
3 {
4 var obj = weakRef . Target ; // Access object if still alive
5 }

How does the garbage collector handle cyclic references in C#?


The garbage collector can handle cyclic references because it uses a **mark-and-sweep** algorithm.
Even if two objects reference each other, if there are no external references to either of them, the
garbage collector will identify them as unreachable and collect them.

What is the impact of finalizers on the generational garbage collector,


and how can they be optimized?
Finalizers can delay garbage collection because objects with finalizers are promoted to the next
generation. To optimize, it’s important to implement the ‘IDisposable‘ pattern and manually
release resources via ‘Dispose()‘, thus avoiding unnecessary finalization.

Explain what a “pinning” of objects in memory is and how it affects


garbage collection.
Pinning an object prevents the garbage collector from moving it in memory, which can lead to
heap fragmentation. This is often done for interop scenarios where unmanaged code needs a stable
memory address. However, pinning should be minimized to reduce fragmentation and improve GC
performance.

313
7.2. Garbage Collection

What is a SafeHandle, and why is it preferred over using finalizers for


resource cleanup?
‘SafeHandle‘ is a class in .NET designed to wrap unmanaged resources like file handles. It provides
a safer and more reliable way to release unmanaged resources compared to finalizers, as it integrates
better with the garbage collector, reducing the risk of resource leaks.

Listing 7.6: Code example

1 using System . Runtime . InteropServices ;


2

3 public class MySafeHandle : SafeHandle


4 {
5 public MySafeHandle () : base ( IntPtr . Zero , true ) { }
6

7 public override bool IsInvalid = > handle == IntPtr . Zero ;


8

9 protected override bool ReleaseHandle ()


10 {
11 // Code to release unmanaged resources
12 return true ;
13 }
14 }

What is the impact of the ‘IDisposable‘ interface on garbage collection,


and how does it improve resource management?
The ‘IDisposable‘ interface provides a mechanism to release unmanaged resources explicitly, avoid-
ing reliance on the garbage collector and finalizers. By calling ‘Dispose()‘, resources can be cleaned
up immediately, improving memory and resource management.

How does the GC.TryStartNoGCRegion method work, and when would


you use it?
‘GC.TryStartNoGCRegion()‘ allows you to prevent garbage collection for a period of time, which
is useful in scenarios requiring high performance without interruption, such as real-time systems.
However, it should be used carefully, as it can lead to an out-of-memory situation if not managed
properly.

314
7.2. Garbage Collection

Can you explain the difference between compacting and non-compacting


garbage collections?
Compacting garbage collection rearranges objects in memory to eliminate gaps caused by deallo-
cated objects, reducing fragmentation. Non-compacting garbage collection, used in the LOH, does
not move objects, which can lead to memory fragmentation but has less overhead.

What are the implications of using large objects in C# in terms of


garbage collection?
Large objects (greater than 85,000 bytes) are allocated in the **Large Object Heap (LOH)**.
The LOH is only collected during a full GC (Gen 2) and is not compacted by default, leading to
potential fragmentation issues.

How does object resurrection work in C#, and what is the impact on
garbage collection?
Object resurrection occurs when an object that is eligible for garbage collection is made reach-
able again by its finalizer. This delays the collection of the object and can complicate memory
management, as the object must be garbage collected again in a later GC cycle.

What are the different modes of garbage collection in .NET (Workstation,


Server), and how do they differ?
.NET supports two main garbage collection modes:
• Workstation GC: Optimized for desktop applications with a focus on minimal GC pause
times..
• Server GC: Optimized for server environments, designed to handle multiple threads and
allocate more resources to garbage collection for higher throughput.

What are the disadvantages of forcing garbage collection using


GC.Collect()?
Manually forcing garbage collection with ‘GC.Collect()‘ can lead to performance degradation, as
it interrupts the normal garbage collection cycles. Additionally, it can cause premature promotion
of objects to higher generations, increasing the cost of future collections.

315
7.3. Performance and Optimization

How does the garbage collector in .NET optimize for latency-sensitive


applications?
The garbage collector can operate in different modes to optimize for latency, such as **Concurrent
GC** or **Low Latency GC**. These modes attempt to minimize pauses during collection by
allowing garbage collection to run concurrently with application code or deferring collection to
reduce interruptions.
Listing 7.7: Code example

1 GCSettings . LatencyMode = GCLatencyMode . LowLatency ;

7.3 Performance and Optimization


How do you identify performance bottlenecks in a C# application?
Performance bottlenecks can be identified using profiling tools such as Visual Studio Profiler, dot-
Trace, or PerfView. These tools can provide detailed insights into CPU usage, memory allocation,
garbage collection, and I/O operations, helping to pinpoint the slowest parts of the application.

What is the role of the Just-In-Time (JIT) compiler in performance op-


timization, and how can JIT impact the startup time of an application?
The JIT compiler compiles IL code to machine code at runtime, which can cause delays during the
initial execution of methods. To mitigate this, you can use **NGen** (Native Image Generator)
to precompile IL into native code or enable **ReadyToRun** (R2R) in .NET Core for improved
startup performance.

What is method inlining in C#, and how does it improve performance?


Method inlining is an optimization where small methods are replaced with their body at the call
site to avoid the overhead of a method call. This can improve performance by reducing stack
frames and avoiding jumps, but it may increase the size of the compiled code (code bloat).

Explain how memory allocation can affect the performance of a C# ap-


plication.
Frequent memory allocations can increase pressure on the garbage collector, leading to more fre-
quent GC cycles, which pause the application. Minimizing allocations, reusing objects, and using
value types where appropriate can improve performance by reducing the frequency of garbage
collection.

316
7.3. Performance and Optimization

What are value types and reference types in C#, and how do they impact
memory allocation and performance?
Value types are stored on the stack and have better performance for short-lived data. Reference
types are stored on the heap and require garbage collection, which adds overhead. Excessive use
of reference types can lead to increased memory allocations and slower performance.

How does caching improve performance, and what are common caching
techniques used in C# applications?
Caching improves performance by storing frequently accessed data in memory, reducing the need
for repeated expensive operations (e.g., database queries). Common caching techniques include
in-memory caching using ‘MemoryCache‘, distributed caching (e.g., Redis), and output caching
for web applications.

What is memory pooling, and how can it be used to optimize performance


in C#?
Memory pooling involves reusing objects or memory buffers instead of frequently allocating and
deallocating them. ‘ArrayPool<T>‘ and ‘ObjectPool<T>‘ in .NET are examples of pooling mech-
anisms that can reduce the pressure on the garbage collector and improve performance in high-
allocation scenarios.

Listing 7.8: Code example

1 var pool = ArrayPool < int >. Shared ;


2 int [] array = pool . Rent (100) ; // Rent an array from the pool
3 // Use the array
4 pool . Return ( array ) ; // Return the array to the pool for reuse

How can asynchronous programming (async/await) improve the perfor-


mance of a C# application?
Asynchronous programming allows for non-blocking I/O operations, freeing up threads to perform
other tasks while waiting for external resources (e.g., network, disk). This improves responsiveness
and scalability in applications that perform I/O-bound tasks.

317
7.3. Performance and Optimization

What are span and memory in C#, and how do they help in improving
performance?
‘Span<T>‘ and ‘Memory<T>‘ are stack-allocated, lightweight types that allow for efficient slicing
of arrays, strings, or memory buffers without additional memory allocations. They reduce heap
allocations and provide more control over memory, leading to performance improvements in high-
performance scenarios like parsing or streaming.

Listing 7.9: Code example

1 Span < int > numbers = stackalloc int [5] { 1 , 2 , 3 , 4 , 5 }; // Stack - allocated array
2 Span < int > slice = numbers . Slice (1 , 3) ; // Efficient slicing without copying

How does the ‘Array.Clear()‘ method compare to setting elements man-


ually in terms of performance?
‘Array.Clear()‘ internally uses highly optimized routines to clear the memory block of the array.
Setting elements manually using a loop is generally slower because it doesn’t use these optimiza-
tions.

How does garbage collection affect the performance of .NET applications,


and how can GC pressure be minimized?
Garbage collection introduces pauses (GC pauses) in application execution, which can impact
performance, especially in real-time applications. GC pressure can be minimized by reducing
memory allocations, reusing objects, pooling memory, and minimizing allocations on the Large
Object Heap (LOH).

What are the advantages of using ‘struct‘ over ‘class‘ in performance-


critical code?
‘struct‘ types are value types and are stored on the stack, which makes them faster to allocate and
deallocate than ‘class‘ types, which are stored on the heap. ‘struct‘s avoid the overhead of garbage
collection, but care must be taken to avoid excessive copying when passing them around.

How does parallelism affect performance, and how can it be implemented


in C#?
Parallelism allows multiple tasks to run concurrently, improving performance by making use
of multi-core processors. In C#, parallelism can be implemented using ‘Parallel.For‘, ‘Paral-

318
7.3. Performance and Optimization

lel.ForEach‘, or ‘Task.WhenAll‘. However, it must be carefully managed to avoid contention,


deadlocks, or excessive context switching.

What is the impact of using ‘Task.Run()‘ vs ‘Task.FromResult()‘ in terms


of performance?
‘Task.Run()‘ creates a new task and schedules it on the thread pool, introducing overhead for
task creation and thread management. ‘Task.FromResult()‘ immediately returns a completed
task without creating a new one, making it much more efficient for returning precomputed or
synchronous results.

How can you optimize LINQ queries for better performance?


LINQ queries can be optimized by:
• Using deferred execution to avoid unnecessary evaluations.
• Avoiding multiple enumerations of collections.
• Using ‘ForEach‘ instead of ‘Select‘ for side effects.
• Using ‘AsParallel()‘ for parallel query execution in CPU-bound scenarios.

What is the cost of boxing and unboxing in C#, and how can you mini-
mize its impact on performance?
Boxing occurs when a value type is converted to a reference type, and unboxing is the reverse
operation. Both operations introduce performance overhead by allocating memory on the heap.
To minimize the impact, avoid frequent boxing/unboxing and use generics to work with value types
directly.

How does array vs. list performance differ, and when would you choose
one over the other?
Arrays offer better performance for fixed-size collections due to their contiguous memory layout,
which allows for faster indexing and lower memory overhead. Lists provide more flexibility with
dynamic resizing but introduce additional overhead. Arrays are preferred when the size is known
and fixed, and lists are preferred when the size is dynamic.

319
7.3. Performance and Optimization

What is the impact of string concatenation in loops, and how can it be


optimized?
String concatenation in loops creates multiple immutable string instances, leading to memory
overhead and increased GC pressure. To optimize, use ‘StringBuilder‘ to concatenate strings
efficiently without creating multiple intermediate string objects.

Listing 7.10: Code example

1 var sb = new StringBuilder () ;


2 for ( int i = 0; i < 100; i ++)
3 {
4 sb . Append ( " Iteration " + i ) ;
5 }
6 string result = sb . ToString () ; // Efficient concatenation

What are the benefits of using ‘readonly‘ fields in terms of performance


optimization?
Marking fields as ‘readonly‘ allows the compiler to optimize them more aggressively since it guaran-
tees that the value won’t change after initialization. This can lead to better performance, especially
in cases where the field is frequently accessed.

What is branch prediction, and how does it affect performance in C#


code?
Branch prediction is a CPU optimization where the processor predicts the outcome of conditional
statements to reduce pipeline stalls. Mis-predicted branches cause performance degradation due
to pipeline flushing. In C#, reducing complex branching and ensuring predictable execution paths
can improve performance.

How can SIMD (Single Instruction, Multiple Data) improve the perfor-
mance of CPU-bound operations in C#?
SIMD allows the CPU to perform the same operation on multiple data points simultaneously,
significantly improving performance for data-parallel operations like vector calculations. In .NET,
SIMD can be leveraged using the ‘System.Numerics.Vector<T>‘ types.

Listing 7.11: Code example

1 using System . Numerics ;


2

3 Vector < float > a = new Vector < float >( new float [] { 1 , 2 , 3 , 4 }) ;

320
7.3. Performance and Optimization

4 Vector < float > b = new Vector < float >( new float [] { 5 , 6 , 7 , 8 }) ;
5 Vector < float > result = a + b ; // Performs addition in parallel

321
8 | Databases, TSQL and ORMs

Databases are a central component of most .NET applications, and a strong understanding of how
to interact with them is essential for building data-driven systems. This includes proficiency in
writing efficient T-SQL queries, managing data integrity, and designing normalized schemas that
support long-term scalability.

In the .NET ecosystem, Object-Relational Mappers (ORMs) like Entity Framework simplify
data access by bridging the gap between relational databases and object-oriented code. Under-
standing how ORMs work, along with their limitations and best practices, is crucial for developing
maintainable and performant applications.

From a job search perspective, interviewers often assess a candidate’s ability to work with
databases effectively. Questions may cover query optimization, understanding of joins and index-
ing, or how to use an ORM to implement repository patterns. Demonstrating this knowledge not
only reinforces your technical breadth but also signals readiness to work on complex, real-world
systems where data access and performance are key.

8.1 SQL Server Basics, Schemas, Tables, Views, Jobs, SSRS,


SSRI, System Databases
What is a schema in SQL Server, and how does it help in managing
database security and organization?
A schema in SQL Server is a container that groups database objects such as tables, views, and
procedures, helping organize and manage permissions. Schemas allow you to apply security at a
higher level by granting or denying access to entire schemas.

Listing 8.1: Code example

1 // Example : Creating a schema in SQL Server


2 CREATE SCHEMA Sales ;
3 GO

322
8.1. SQL Server Basics, Schemas, Tables, Views, Jobs, SSRS, SSRI, System Databases

4 // Creating a table within the schema


5 CREATE TABLE Sales . Orders
6 (
7 OrderID INT PRIMARY KEY ,
8 OrderDate DATETIME
9 );
10 GO

What are the system databases in SQL Server, and what are their pur-
poses?
SQL Server has four main system databases: ‘master‘, ‘msdb‘, ‘model‘, and ‘tempdb‘.
• master: Contains server-wide configuration, such as login information and system-level
metadata.
• msdb: Stores information for scheduling jobs and backups.
• model: Serves as a template for creating new databases.
• tempdb: Used for temporary storage of objects, temporary tables, and session-related data.
Listing 8.2: Code example

1 // Example : Viewing system databases in SQL Server


2 SELECT name FROM sys . databases WHERE database_id < 5;

What is the difference between a table and a view in SQL Server?


A table is a physical storage of data in a database, whereas a view is a virtual table based on a
SELECT query. Views do not store data themselves; they display data from underlying tables and
can simplify complex queries or restrict access to specific data.
Listing 8.3: Code example

1 // Example : Creating a table and a view


2 CREATE TABLE Employees
3 (
4 EmployeeID INT PRIMARY KEY ,
5 Name VARCHAR (100) ,
6 DepartmentID INT
7 );
8 GO
9

10 CREATE VIEW DepartmentView AS


11 SELECT EmployeeID , Name
12 FROM Employees
13 WHERE DepartmentID = 1;
14 GO

323
8.1. SQL Server Basics, Schemas, Tables, Views, Jobs, SSRS, SSRI, System Databases

What are SQL Server Jobs, and how do you create and schedule them?
SQL Server Jobs are automated tasks, managed through SQL Server Agent, that can run at
specified times or intervals. They can include tasks like backups, data imports/exports, or report
generation.

Listing 8.4: Code example

1 // Example : Scheduling a job to run every day at midnight


2 USE msdb ;
3 GO
4 EXEC sp_add_job @job_name = ’ DailyBackup ’;
5 EXEC sp_add_jobstep @job_name = ’ DailyBackup ’ , @step_name = ’ BackupStep ’ ,
6 @subsystem = ’ TSQL ’ , @command = ’ BACKUP DATABASE [ MyDatabase ] TO DISK = N ’ ’C :\
Backup \ MyDatabase . bak ’ ’ ’;
7 EXEC sp_add_schedule @schedule_name = ’ DailySchedule ’ , @freq_type = 4 ,
@active_start_time = 000000;
8 EXEC sp_attach_schedule @job_name = ’ DailyBackup ’ , @schedule_name = ’ DailySchedule
’;
9 EXEC sp_start_job @job_name = ’ DailyBackup ’;
10 GO

What is SSRS (SQL Server Reporting Services), and how does it integrate
with SQL Server?
SSRS is a server-based report generation tool that allows the creation, management, and delivery
of reports. It integrates with SQL Server to pull data from databases and present it in various
formats (e.g., charts, tables) via web browsers, email, or file shares.

Listing 8.5: Code example

1 // Example : Querying SQL Server for a report in SSRS


2 SELECT SalesOrderID , OrderDate , TotalDue
3 FROM Sales . SalesOrderHeader
4 WHERE OrderDate BETWEEN ’ 2023 -01 -01 ’ AND ’ 2023 -12 -31 ’;

What is the role of ‘tempdb‘ in SQL Server, and what performance con-
siderations should be taken when configuring it?
‘tempdb‘ is a system database used for storing temporary tables, temporary stored procedures, and
other temporary objects. It is also used for sorting results during query execution. Performance
considerations include using fast storage (SSD) and ensuring sufficient space, as heavy use of
‘tempdb‘ can cause bottlenecks.

Listing 8.6: Code example

324
8.1. SQL Server Basics, Schemas, Tables, Views, Jobs, SSRS, SSRI, System Databases

1 // Example : Querying tempdb usage in SQL Server


2 SELECT SUM ( unallocated_extent_page_count ) AS FreePages , SUM (
allocated_extent_page_count ) AS AllocatedPages
3 FROM sys . dm_db_file_space_usage ;

How do you optimize a table for faster querying in SQL Server?


You can optimize a table by adding appropriate indexes, partitioning large tables, denormalizing
data where necessary, and ensuring that queries are optimized to avoid table scans. Indexing
should be applied based on the columns frequently used in ‘WHERE‘, ‘JOIN‘, and ‘ORDER BY‘
clauses.

Listing 8.7: Code example

1 // Example : Creating an index to optimize queries on a large table


2 CREATE INDEX idx_EmployeeName
3 ON Employees ( Name ) ;
4 GO

What is a stored procedure, and how is it different from a view in SQL


Server?
A stored procedure is a compiled group of SQL statements that can accept input parameters,
perform operations, and return results. Unlike views, stored procedures can include logic like
loops, conditional statements, and error handling.

Listing 8.8: Code example

1 // Example : Creating a stored procedure


2 CREATE PROCEDURE GetEmployeeByID @EmployeeID INT
3 AS
4 BEGIN
5 SELECT EmployeeID , Name , DepartmentID
6 FROM Employees
7 WHERE EmployeeID = @EmployeeID ;
8 END
9 GO

How do you manage permissions for a schema in SQL Server?


Permissions can be managed at the schema level by granting or denying access to specific users or
roles. This allows you to control access to all objects within the schema in one operation, rather
than managing permissions on individual objects.

325
8.1. SQL Server Basics, Schemas, Tables, Views, Jobs, SSRS, SSRI, System Databases

Listing 8.9: Code example

1 // Example : Granting SELECT permission on a schema


2 GRANT SELECT ON SCHEMA :: Sales TO [ UserOrRole ];
3 GO

What is a clustered index, and how does it differ from a non-clustered


index?
A clustered index determines the physical order of data in the table and is usually created on the
primary key. A table can have only one clustered index. A non-clustered index, on the other hand,
maintains a separate structure from the data and allows for multiple non-clustered indexes on a
table.

Listing 8.10: Code example

1 // Example : Creating a clustered and non - clustered index


2 CREATE CLUSTERED INDEX idx_EmployeeID ON Employees ( EmployeeID ) ;
3 CREATE NONCLUSTERED INDEX idx_EmployeeName ON Employees ( Name ) ;
4 GO

What is the difference between ‘INNER JOIN‘ and ‘OUTER JOIN‘ in


SQL Server?
‘INNER JOIN‘ returns rows where there is a match in both tables, whereas ‘OUTER JOIN‘ returns
all rows from one table and the matching rows from the other, with ‘NULL‘ values if there is no
match. ‘LEFT OUTER JOIN‘ returns all rows from the left table, and ‘RIGHT OUTER JOIN‘
returns all rows from the right table.

Listing 8.11: Code example

1 // Example : Using INNER JOIN and OUTER JOIN


2 -- INNER JOIN
3 SELECT Employees . Name , Departments . DepartmentName
4 FROM Employees
5 INNER JOIN Departments ON Employees . DepartmentID = Departments . DepartmentID ;
6

7 -- LEFT OUTER JOIN


8 SELECT Employees . Name , Departments . DepartmentName
9 FROM Employees
10 LEFT JOIN Departments ON Employees . DepartmentID = Departments . DepartmentID ;
11 GO

326
8.1. SQL Server Basics, Schemas, Tables, Views, Jobs, SSRS, SSRI, System Databases

What is a CROSS JOIN in SQL Server?


A ‘CROSS JOIN‘ produces the Cartesian product of two tables, meaning it combines each row
from the first table with every row from the second table. It is useful when you want to pair all
possible combinations of rows between two tables.

Listing 8.12: Code example

1 // Example : Using CROSS JOIN


2 -- Assume we have two tables : Products and Categories
3 -- Products Table
4 -- + - - - -+ - - - - - - - - - - - -+
5 -- | ID | Name |
6 -- + - - - -+ - - - - - - - - - - - -+
7 -- | 1 | Laptop |
8 -- | 2 | Smartphone |
9 -- + - - - -+ - - - - - - - - - - - -+
10

11 -- Categories Table
12 -- + - - - -+ - - - - - - - - - - -+
13 -- | ID | Category |
14 -- + - - - -+ - - - - - - - - - - -+
15 -- | 1 | Electronics |
16 -- | 2 | Accessories |
17 -- + - - - -+ - - - - - - - - - - -+
18

19 -- CROSS JOIN Query


20 SELECT Products . Name AS Product , Categories . Category
21 FROM Products
22 CROSS JOIN Categories ;
23

24 -- Result
25 -- + - - - - - - - - - - - -+ - - - - - - - - - - - - -+
26 -- | Product | Category |
27 -- + - - - - - - - - - - - -+ - - - - - - - - - - - - -+
28 -- | Laptop | Electronics |
29 -- | Laptop | Accessories |
30 -- | Smartphone | Electronics |
31 -- | Smartphone | Accessories |
32 -- + - - - - - - - - - - - -+ - - - - - - - - - - - - -+
33 GO

What is a SELF JOIN in SQL Server?


A ‘SELF JOIN‘ is a join where a table is joined with itself. This is useful when you need to compare
rows within the same table, such as finding relationships between data in the same dataset.

327
8.1. SQL Server Basics, Schemas, Tables, Views, Jobs, SSRS, SSRI, System Databases

Listing 8.13: Code example

1 // Example : Using SELF JOIN


2 -- Assume we have an Employees table
3 -- Employees Table
4 -- + - - - -+ - - - - - - - - - -+ - - - - - - - - - - -+
5 -- | ID | Name | ManagerID |
6 -- + - - - -+ - - - - - - - - - -+ - - - - - - - - - - -+
7 -- | 1 | John | NULL |
8 -- | 2 | Sarah | 1 |
9 -- | 3 | Mike | 1 |
10 -- | 4 | Anna | 2 |
11 -- + - - - -+ - - - - - - - - - -+ - - - - - - - - - - -+
12

13 -- SELF JOIN Query to find employees and their managers


14 SELECT E1 . Name AS Employee , E2 . Name AS Manager
15 FROM Employees E1
16 LEFT JOIN Employees E2 ON E1 . ManagerID = E2 . ID ;
17

18 -- Result
19 -- + - - - - - - - - - -+ - - - - - - - - - -+
20 -- | Employee | Manager |
21 -- + - - - - - - - - - -+ - - - - - - - - - -+
22 -- | John | NULL |
23 -- | Sarah | John |
24 -- | Mike | John |
25 -- | Anna | Sarah |
26 -- + - - - - - - - - - -+ - - - - - - - - - -+
27 GO

What are SQL constraints?


SQL constraints are rules applied to columns in a table that help ensure the accuracy and reliability
of the data within the database. They enforce data integrity by restricting the types of data
that can be inserted into a column, preventing invalid or inconsistent data. Examples of SQL
constraints:
• NOT NULL: Ensures that a column cannot have a ‘NULL‘ value.
• UNIQUE: Ensures that all values in a column are different.
• PRIMARY KEY: Uniquely identifies each record in a table and combines ‘NOT NULL‘
and ‘UNIQUE‘.
• FOREIGN KEY: Ensures referential integrity by linking two tables.
• CHECK: Ensures that all values in a column satisfy a specific condition.
• DEFAULT: Sets a default value for a column when no value is specified.

Listing 8.14: Code example

328
8.1. SQL Server Basics, Schemas, Tables, Views, Jobs, SSRS, SSRI, System Databases

1 // Example : Adding SQL constraints


2 CREATE TABLE Employees (
3 EmployeeID INT NOT NULL ,
4 Email VARCHAR (255) UNIQUE ,
5 Salary DECIMAL (10 , 2) CHECK ( Salary > 0) ,
6 DepartmentID INT ,
7 PRIMARY KEY ( EmployeeID ) ,
8 FOREIGN KEY ( DepartmentID ) REFERENCES Departments ( DepartmentID )
9 );
10 GO

What is a ‘PRIMARY KEY‘ constraint in SQL Server, and how is it


different from a ‘UNIQUE‘ constraint?
A ‘PRIMARY KEY‘ constraint ensures that each row in a table has a unique identifier, and it
cannot contain ‘NULL‘ values. A ‘UNIQUE‘ constraint also ensures uniqueness but allows for one
‘NULL‘ value. A table can have only one ‘PRIMARY KEY‘ but multiple ‘UNIQUE‘ constraints.

Listing 8.15: Code example

1 // Example : Adding a PRIMARY KEY and UNIQUE constraint


2 ALTER TABLE Employees ADD CONSTRAINT PK_EmployeeID PRIMARY KEY ( EmployeeID ) ;
3 ALTER TABLE Employees ADD CONSTRAINT UQ_EmployeeEmail UNIQUE ( Email ) ;
4 GO

How do you use ‘TRY...CATCH‘ blocks for error handling in SQL Server?
‘TRY...CATCH‘ blocks are used to handle runtime errors in SQL Server. If an error occurs in the
‘TRY‘ block, control is transferred to the ‘CATCH‘ block, where you can log the error or take
corrective actions.

Listing 8.16: Code example

1 // Example : Using TRY ... CATCH for error handling


2 BEGIN TRY
3 -- Code that may throw an error
4 INSERT INTO Employees ( EmployeeID , Name ) VALUES (1 , ’ John ’) ;
5 END TRY
6 BEGIN CATCH
7 -- Error handling code
8 SELECT ERROR_MESSAGE () AS ErrorMessage ;
9 END CATCH ;
10 GO

329
8.1. SQL Server Basics, Schemas, Tables, Views, Jobs, SSRS, SSRI, System Databases

What are table-valued functions in SQL Server, and how do they differ
from scalar functions?
A table-valued function returns a table data type, which can be used like a table in ‘SELECT‘
statements. Scalar functions return a single value. Table-valued functions are more flexible and
can return complex data structures, whereas scalar functions are limited to returning one value.

Listing 8.17: Code example

1 // Example : Creating a table - valued function


2 CREATE FUNCTION GetEmployeesByDepartment ( @DepartmentID INT )
3 RETURNS TABLE
4 AS
5 RETURN
6 (
7 SELECT EmployeeID , Name
8 FROM Employees
9 WHERE DepartmentID = @DepartmentID
10 );
11 GO

How do you use partitioning to improve query performance in SQL


Server?
Partitioning divides a large table into smaller, more manageable pieces based on a partition key,
improving performance by allowing queries to only scan relevant partitions. This can reduce I/O
and improve response times for large tables.

Listing 8.18: Code example

1 // Example : Creating a partitioned table


2 CREATE PARTITION FUNCTION PartitionOrdersByYear ( INT )
3 AS RANGE LEFT FOR VALUES (2019 , 2020 , 2021) ;
4 GO
5

6 CREATE PARTITION SCHEME OrderPartitionScheme


7 AS PARTITION PartitionOrdersByYear
8 TO ( FileGroup1 , FileGroup2 , FileGroup3 , FileGroup4 ) ;
9 GO
10

11 CREATE TABLE Orders


12 (
13 OrderID INT PRIMARY KEY ,
14 OrderDate DATE
15 )
16 ON OrderPartitionScheme ( YEAR ( OrderDate ) ) ;

330
8.1. SQL Server Basics, Schemas, Tables, Views, Jobs, SSRS, SSRI, System Databases

17 GO

How do you back up and restore a database in SQL Server?


Backing up a database creates a copy of its data, allowing it to be restored in case of failure or data
loss. The ‘BACKUP DATABASE‘ and ‘RESTORE DATABASE‘ commands are used for these
operations, and different types of backups can be performed (e.g., full, differential, log backups).

Listing 8.19: Code example

1 // Example : Performing a full database backup and restoring it


2 -- Backup
3 BACKUP DATABASE MyDatabase TO DISK = ’C :\ Backups \ MyDatabase . bak ’;
4 GO
5

6 -- Restore
7 RESTORE DATABASE MyDatabase FROM DISK = ’C :\ Backups \ MyDatabase . bak ’;
8 GO

How do you use ‘MERGE‘ in SQL Server, and when is it useful?


‘MERGE‘ combines ‘INSERT‘, ‘UPDATE‘, and ‘DELETE‘ operations in a single statement, al-
lowing you to synchronize two tables by matching records and taking appropriate actions. It is
useful for data warehousing, ETL processes, and managing slowly changing dimensions.

Listing 8.20: Code example

1 // Example : Using MERGE to synchronize tables


2 MERGE INTO TargetTable AS target
3 USING SourceTable AS source
4 ON target . ID = source . ID
5 WHEN MATCHED THEN
6 UPDATE SET target . Value = source . Value
7 WHEN NOT MATCHED BY TARGET THEN
8 INSERT ( ID , Value ) VALUES ( source . ID , source . Value )
9 WHEN NOT MATCHED BY SOURCE THEN
10 DELETE ;
11 GO

331
8.2. T-SQL (Transact-SQL), Stored Procedures (SProcs),
Views, Functions, etc

8.2 T-SQL (Transact-SQL), Stored Procedures (SProcs),


Views, Functions, etc
What is the difference between a stored procedure (SProc) and a function
in T-SQL?
Stored procedures (SProcs) can perform actions such as inserting, updating, or deleting data and
can return multiple result sets. Functions, however, are used for computations and must return a
value or a table. Functions cannot modify data (e.g., INSERT, UPDATE, DELETE) except for
certain table-valued functions.

Listing 8.21: Code example

1 // Example : Stored procedure vs function


2 -- Stored Procedure
3 CREATE PROCEDURE GetEmployeeDetails
4 AS
5 BEGIN
6 SELECT * FROM Employees ;
7 END
8 GO
9

10 -- Function
11 CREATE FUNCTION GetEmployeeName ( @EmployeeID INT )
12 RETURNS VARCHAR (100)
13 AS
14 BEGIN
15 DECLARE @EmployeeName VARCHAR (100) ;
16 SELECT @EmployeeName = Name FROM Employees WHERE EmployeeID = @EmployeeID ;
17 RETURN @EmployeeName ;
18 END
19 GO

How do you pass parameters to a stored procedure in T-SQL?


Parameters are passed to a stored procedure by specifying them in the procedure definition and
passing values when calling the procedure. Parameters can be input parameters, output parame-
ters, or both.

Listing 8.22: Code example

1 // Example : Passing parameters to a stored procedure


2 CREATE PROCEDURE GetEmployeeByID
3 @EmployeeID INT
4 AS

332
8.2. T-SQL (Transact-SQL), Stored Procedures (SProcs),
Views, Functions, etc

5 BEGIN
6 SELECT * FROM Employees WHERE EmployeeID = @EmployeeID ;
7 END
8 GO
9

10 -- Executing the stored procedure with a parameter


11 EXEC GetEmployeeByID @EmployeeID = 1;
12 GO

What are table-valued functions in T-SQL, and when would you use
them?
Table-valued functions return a table as their result and can be used like a regular table in ‘SE-
LECT‘ statements. They are useful when you need to encapsulate a reusable query that returns
multiple rows.

Listing 8.23: Code example

1 // Example : Table - valued function


2 CREATE FUNCTION GetEmployeesByDepartment ( @DepartmentID INT )
3 RETURNS TABLE
4 AS
5 RETURN
6 (
7 SELECT EmployeeID , Name
8 FROM Employees
9 WHERE DepartmentID = @DepartmentID
10 );
11 GO
12

13 -- Using the function


14 SELECT * FROM GetEmployeesByDepartment (1) ;
15 GO

How do you handle error handling in T-SQL using TRY...CATCH blocks?


‘TRY...CATCH‘ blocks in T-SQL are used to handle runtime errors. If an error occurs in the
‘TRY‘ block, control is transferred to the ‘CATCH‘ block, where you can handle the error or log
it.

Listing 8.24: Code example

1 // Example : TRY ... CATCH error handling in T - SQL


2 BEGIN TRY
3 -- Code that may throw an error
4 INSERT INTO Employees ( EmployeeID , Name ) VALUES (1 , ’ John Doe ’) ;

333
8.2. T-SQL (Transact-SQL), Stored Procedures (SProcs),
Views, Functions, etc

5 END TRY
6 BEGIN CATCH
7 -- Error handling code
8 SELECT ERROR_MESSAGE () AS ErrorMessage ;
9 END CATCH ;
10 GO

What is a CTE (Common Table Expression) in T-SQL, and how is it


different from a subquery?
A CTE is a temporary result set that you can reference within a ‘SELECT‘, ‘INSERT‘, ‘UPDATE‘,
or ‘DELETE‘ statement. Unlike a subquery, a CTE can be self-referencing (recursive), making it
useful for hierarchical queries.

Listing 8.25: Code example

1 // Example : Using a CTE in T - SQL


2 WITH EmployeeCTE AS
3 (
4 SELECT EmployeeID , Name , ManagerID
5 FROM Employees
6 WHERE ManagerID IS NULL
7 UNION ALL
8 SELECT e . EmployeeID , e . Name , e . ManagerID
9 FROM Employees e
10 INNER JOIN EmployeeCTE cte ON e . ManagerID = cte . EmployeeID
11 )
12 SELECT * FROM EmployeeCTE ;
13 GO

How do you create a view in T-SQL, and when would you use one?
A view is a virtual table based on a SELECT query. You would use a view to simplify complex
queries, encapsulate business logic, or restrict access to specific data. Views do not store data
themselves; they display data from the underlying tables.

Listing 8.26: Code example

1 // Example : Creating a view in T - SQL


2 CREATE VIEW EmployeeView AS
3 SELECT EmployeeID , Name , DepartmentID
4 FROM Employees
5 WHERE DepartmentID = 1;
6 GO
7

8 -- Using the view

334
8.2. T-SQL (Transact-SQL), Stored Procedures (SProcs),
Views, Functions, etc

9 SELECT * FROM EmployeeView ;


10 GO

How do you implement dynamic SQL in a stored procedure using


sp_executesql?
Dynamic SQL allows you to build SQL statements dynamically at runtime and execute them using
sp_executesql. This is useful for scenarios where query parameters or conditions are not known
until runtime.

Listing 8.27: Code example

1 // Example : Using sp_executesql for dynamic SQL


2 DECLARE @SQL NVARCHAR (1000) ;
3 DECLARE @DepartmentID INT = 1;
4

5 SET @SQL = ’ SELECT EmployeeID , Name FROM Employees WHERE DepartmentID = @DeptID ’;
6 EXEC sp_executesql @SQL , N ’ @DeptID INT ’ , @DeptID = @DepartmentID ;
7 GO

How do you handle transactions in T-SQL, and what are the differences
between COMMIT and ROLLBACK?
Transactions in T-SQL are used to ensure that a sequence of SQL operations is atomic, consistent,
isolated, and durable (ACID). You can use ‘BEGIN TRANSACTION‘ to start a transaction,
‘COMMIT‘ to save the changes, and ‘ROLLBACK‘ to undo them.

Listing 8.28: Code example

1 // Example : Using transactions in T - SQL


2 BEGIN TRANSACTION ;
3

4 INSERT INTO Employees ( EmployeeID , Name ) VALUES (1 , ’ John Doe ’) ;


5 INSERT INTO Employees ( EmployeeID , Name ) VALUES (2 , ’ Jane Doe ’) ;
6

7 -- If no errors occur , commit the transaction


8 COMMIT ;
9 GO
10

11 -- If an error occurs , rollback the transaction


12 ROLLBACK ;
13 GO

335
8.2. T-SQL (Transact-SQL), Stored Procedures (SProcs),
Views, Functions, etc

What is a scalar function in T-SQL, and how do you create one?


A scalar function in T-SQL returns a single value. Scalar functions can be used in ‘SELECT‘
statements, ‘WHERE‘ clauses, or any place where a single value is required.

Listing 8.29: Code example

1 // Example : Creating a scalar function


2 CREATE FUNCTION GetFullName ( @FirstName VARCHAR (50) , @LastName VARCHAR (50) )
3 RETURNS VARCHAR (100)
4 AS
5 BEGIN
6 RETURN @FirstName + ’ ’ + @LastName ;
7 END
8 GO
9

10 -- Using the function


11 SELECT GetFullName ( ’ John ’ , ’ Doe ’) ;
12 GO

What is a recursive CTE, and how is it used in T-SQL?


A recursive CTE is a CTE that references itself. It is useful for traversing hierarchical or tree-
structured data such as organizational charts or file systems.

Listing 8.30: Code example

1 // Example : Using a recursive CTE in T - SQL


2 WITH RecursiveCTE AS
3 (
4 SELECT EmployeeID , Name , ManagerID
5 FROM Employees
6 WHERE ManagerID IS NULL
7 UNION ALL
8 SELECT e . EmployeeID , e . Name , e . ManagerID
9 FROM Employees e
10 INNER JOIN RecursiveCTE cte ON e . ManagerID = cte . EmployeeID
11 )
12 SELECT * FROM RecursiveCTE ;
13 GO

What is a CROSS APPLY in T-SQL, and how is it different from an


INNER JOIN?
‘CROSS APPLY‘ is used to join each row from the left table to the result of a function or a
subquery on the right table. It is different from ‘INNER JOIN‘ because ‘CROSS APPLY‘ can

336
8.2. T-SQL (Transact-SQL), Stored Procedures (SProcs),
Views, Functions, etc

handle table-valued functions and return different results for each row.

Listing 8.31: Code example

1 // Example : Using CROSS APPLY in T - SQL


2 SELECT e . EmployeeID , e . Name , f . EmployeeName
3 FROM Employees e
4 CROSS APPLY ( SELECT Name AS EmployeeName FROM Employees WHERE ManagerID = e .
EmployeeID ) AS f ;
5 GO

What is the purpose of ‘ROW_NUMBER()‘ in T-SQL, and how is it


typically used?
‘ROW_NUMBER()‘ is a window function that assigns a unique row number to rows in a result
set, starting from 1 for each partition or set. It is commonly used for pagination or when needing
to return a specific subset of data from a large result set.

Listing 8.32: Code example

1 // Example : Using ROW_NUMBER () for pagination in T - SQL


2 SELECT EmployeeID , Name , ROW_NUMBER () OVER ( ORDER BY EmployeeID ) AS RowNum
3 FROM Employees
4 WHERE DepartmentID = 1;
5 GO

How do you use ‘CASE‘ statements in T-SQL for conditional logic?


‘CASE‘ statements in T-SQL allow you to apply conditional logic within queries, similar to
‘IF...THEN‘ logic. It can be used in ‘SELECT‘, ‘UPDATE‘, and ‘WHERE‘ clauses to return
different values based on conditions.

Listing 8.33: Code example

1 // Example : Using CASE statement in T - SQL


2 SELECT EmployeeID , Name ,
3 CASE
4 WHEN DepartmentID = 1 THEN ’ Sales ’
5 WHEN DepartmentID = 2 THEN ’ HR ’
6 ELSE ’ Other ’
7 END AS DepartmentName
8 FROM Employees ;
9 GO

337
8.2. T-SQL (Transact-SQL), Stored Procedures (SProcs),
Views, Functions, etc

How do you create a trigger in T-SQL, and what are the different types
of triggers?
A trigger is a special type of stored procedure that automatically executes in response to certain
events (e.g., ‘INSERT‘, ‘UPDATE‘, ‘DELETE‘) on a table or view. Triggers can be used for
auditing or enforcing business rules.

Listing 8.34: Code example

1 // Example : Creating a trigger for INSERT operations


2 CREATE TRIGGER trg_AuditInsert
3 ON Employees
4 AFTER INSERT
5 AS
6 BEGIN
7 INSERT INTO AuditLog ( Action , ActionDate )
8 VALUES ( ’ INSERT ’ , GETDATE () ) ;
9 END
10 GO

How do you optimize a T-SQL query for performance using indexes?


Indexes improve query performance by allowing the database to find rows faster without scanning
the entire table. You can create indexes on columns frequently used in ‘WHERE‘, ‘JOIN‘, and
‘ORDER BY‘ clauses.

Listing 8.35: Code example

1 // Example : Creating an index on a column


2 CREATE INDEX idx_EmployeeName
3 ON Employees ( Name ) ;
4 GO

How do you return multiple result sets from a stored procedure in T-


SQL?
You can return multiple result sets from a stored procedure by including multiple ‘SELECT‘
statements within the procedure. Each ‘SELECT‘ statement will return its own result set.

Listing 8.36: Code example

1 // Example : Returning multiple result sets from a stored procedure


2 CREATE PROCEDURE GetMultipleResults
3 AS
4 BEGIN
5 SELECT * FROM Employees ;

338
8.2. T-SQL (Transact-SQL), Stored Procedures (SProcs),
Views, Functions, etc

6 SELECT * FROM Departments ;


7 END
8 GO
9

10 -- Executing the stored procedure


11 EXEC GetMultipleResults ;
12 GO

How do you implement paging in T-SQL using the ‘OFFSET‘ and


‘FETCH‘ keywords?
Paging allows you to return a subset of results by specifying the number of rows to skip (‘OFFSET‘)
and the number of rows to return (‘FETCH‘). It is commonly used in applications to display data
in chunks.

Listing 8.37: Code example

1 // Example : Implementing paging using OFFSET and FETCH in T - SQL


2 SELECT EmployeeID , Name
3 FROM Employees
4 ORDER BY EmployeeID
5 OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY ;
6 GO

How do you update data in T-SQL using a JOIN between two tables?
You can update data in one table based on a condition in another table by using a ‘JOIN‘ in the
‘UPDATE‘ statement. This allows you to modify data across related tables in a single query.

Listing 8.38: Code example

1 // Example : Updating data using a JOIN in T - SQL


2 UPDATE e
3 SET e . Salary = e . Salary + 500
4 FROM Employees e
5 INNER JOIN Departments d ON e . DepartmentID = d . DepartmentID
6 WHERE d . DepartmentName = ’ HR ’;
7 GO

339
8.3. ORM Concepts and Entity Framework

8.3 ORM Concepts and Entity Framework


What is an ORM, and why is it useful in application development?
An ORM (Object-Relational Mapper) is a tool that automates the conversion of data between
incompatible systems, typically between object-oriented programming languages and relational
databases. It allows developers to work with database records as objects, simplifying database
interactions and reducing the need for writing SQL queries.

Listing 8.39: Code example

1 // Example : Basic ORM concept using Entity Framework


2 using ( var context = new MyDbContext () )
3 {
4 // Adding a new object , which will translate into an SQL INSERT
5 context . Employees . Add ( new Employee { Name = " John Doe " }) ;
6 context . SaveChanges () ; // Saves changes to the database
7 }

What are the differences between ‘DbContext‘ and ‘ObjectContext‘ in


Entity Framework?
‘DbContext‘ is a simpler and more modern API introduced in Entity Framework 4.1, designed
to make working with EF more intuitive. ‘ObjectContext‘ is part of the older EF API and is
more complex but offers greater control and customization. ‘DbContext‘ is a wrapper around
‘ObjectContext‘, and most new development should use ‘DbContext‘.

Listing 8.40: Code example

1 // Example : Using DbContext to interact with a database


2 public class MyDbContext : DbContext
3 {
4 public DbSet < Employee > Employees { get ; set ; }
5 }

How does Entity Framework handle lazy loading, and what are the po-
tential pitfalls?
Lazy loading in Entity Framework delays the loading of related entities until they are accessed
in code. This can lead to performance issues if not managed properly, such as the "N+1 query
problem," where multiple queries are sent to the database, one for each related entity.

Listing 8.41: Code example

340
8.3. ORM Concepts and Entity Framework

1 // Example : Lazy loading in Entity Framework


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

7 // Navigation property
8 public virtual Department Department { get ; set ; } // This will be lazily
loaded
9 }

What is eager loading in Entity Framework, and how is it different from


lazy loading?
Eager loading in Entity Framework retrieves related entities as part of the initial query, avoiding
the need for additional queries later. This is useful for optimizing performance when you know
that related data will be needed immediately.

Listing 8.42: Code example

1 // Example : Eager loading in Entity Framework using Include


2 var employees = context . Employees . Include ( e = > e . Department ) . ToList () ;

What is the purpose of the ‘DbSet‘ class in Entity Framework?


‘DbSet‘ represents a collection of entities that you can query from the database or save changes
to. It acts as a gateway for CRUD operations in Entity Framework, abstracting the underlying
SQL and database structure.

Listing 8.43: Code example

1 // Example : Using DbSet to perform CRUD operations


2 public class MyDbContext : DbContext
3 {
4 public DbSet < Employee > Employees { get ; set ; }
5 }
6

7 // Usage
8 var employee = context . Employees . Find (1) ; // Fetches employee with ID 1
9 context . Employees . Add ( new Employee { Name = " Jane Doe " }) ;
10 context . SaveChanges () ;

341
8.3. ORM Concepts and Entity Framework

How does Entity Framework handle change tracking, and why is it im-
portant?
Entity Framework automatically tracks changes to entities fetched from the database so that when
‘SaveChanges()‘ is called, it can generate the appropriate SQL statements (‘INSERT‘, ‘UPDATE‘,
‘DELETE‘). Change tracking is important for ensuring that only modified entities are persisted
back to the database.

Listing 8.44: Code example

1 // Example : Change tracking in Entity Framework


2 var employee = context . Employees . Find (1) ;
3 employee . Name = " Updated Name " ; // EF automatically tracks this change
4 context . SaveChanges () ; // Generates an SQL UPDATE statement

What is code-first development in Entity Framework, and how does it


differ from database-first?
Code-first development allows you to define your database schema using C# classes, and Entity
Framework will generate the corresponding database. Database-first development, on the other
hand, starts with an existing database schema, and Entity Framework generates C# classes based
on that schema.

Listing 8.45: Code example

1 // Example : Code - first model class


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

8 // Using DbContext for code - first


9 public class MyDbContext : DbContext
10 {
11 public DbSet < Employee > Employees { get ; set ; }
12 }

How does Entity Framework handle migrations, and why are they im-
portant?
Migrations in Entity Framework allow you to incrementally apply changes to your database schema
based on changes made to your model classes. This ensures that the database stays in sync with
the application’s data model without requiring manual database updates.

342
8.3. ORM Concepts and Entity Framework

Listing 8.46: Code example

1 // Example : Adding a migration


2 Add - Migration AddBirthDateToEmployee
3

4 // Example : Applying the migration


5 Update - Database

What are complex types in Entity Framework, and how do they differ
from entities?
Complex types in Entity Framework are non-entity types that cannot have a primary key and
cannot exist independently in the database. They are typically used to represent a set of related
properties, such as an address, that do not require their own table.

Listing 8.47: Code example

1 // Example : Using a complex type in Entity Framework


2 public class Address
3 {
4 public string Street { get ; set ; }
5 public string City { get ; set ; }
6 }
7

8 public class Employee


9 {
10 public int EmployeeId { get ; set ; }
11 public string Name { get ; set ; }
12 public Address Address { get ; set ; } // Complex type
13 }

How do you handle many-to-many relationships in Entity Framework?


Entity Framework supports many-to-many relationships by using a join table. In code-first devel-
opment, you can define a many-to-many relationship using navigation properties on both entities,
and Entity Framework will automatically create the join table.

Listing 8.48: Code example

1 // Example : Many - to - many relationship in Entity Framework


2 public class Employee
3 {
4 public int EmployeeId { get ; set ; }
5 public string Name { get ; set ; }
6 public virtual ICollection < Project > Projects { get ; set ; }
7 }

343
8.3. ORM Concepts and Entity Framework

9 public class Project


10 {
11 public int ProjectId { get ; set ; }
12 public string ProjectName { get ; set ; }
13 public virtual ICollection < Employee > Employees { get ; set ; }
14 }

How does Entity Framework handle concurrency, and what are some
common strategies for resolving concurrency conflicts?
Entity Framework handles concurrency using optimistic concurrency control, where the database is
checked for conflicts before changes are saved. A common strategy to resolve concurrency conflicts
is to use a ‘RowVersion‘ column, which is checked to ensure that the entity hasn’t been modified
by another user since it was retrieved.
Listing 8.49: Code example

1 // Example : Using a RowVersion for concurrency control


2 public class Employee
3 {
4 public int EmployeeId { get ; set ; }
5 public string Name { get ; set ; }
6 [ Timestamp ]
7 public byte [] RowVersion { get ; set ; } // Used for concurrency checking
8 }

What is the purpose of the ‘AsNoTracking()‘ method in Entity Frame-


work, and when would you use it?
The ‘AsNoTracking()‘ method disables change tracking for a query, which can improve performance
when you do not need to modify the retrieved entities. This is particularly useful for read-only
operations where tracking changes is unnecessary overhead.
Listing 8.50: Code example

1 // Example : Using AsNoTracking for a read - only query


2 var employees = context . Employees . AsNoTracking () . ToList () ;

How do you handle raw SQL queries in Entity Framework?


Entity Framework allows you to execute raw SQL queries using the ‘FromSqlRaw‘ or ‘ExecuteSql-
Raw‘ methods for complex queries that cannot easily be expressed using LINQ or when you need
finer control over the SQL.

344
8.3. ORM Concepts and Entity Framework

Listing 8.51: Code example

1 // Example : Executing a raw SQL query in Entity Framework


2 var employees = context . Employees
3 . FromSqlRaw ( " SELECT * FROM Employees WHERE DepartmentId = {0} " , departmentId )
4 . ToList () ;

What are navigation properties in Entity Framework, and how are they
useful?
Navigation properties in Entity Framework allow you to navigate relationships between entities.
They represent a link between two entities, making it easier to retrieve related data without writing
complex joins.

Listing 8.52: Code example

1 // Example : Navigation properties in Entity Framework


2 public class Employee
3 {
4 public int EmployeeId { get ; set ; }
5 public string Name { get ; set ; }
6 public int DepartmentId { get ; set ; }
7

8 // Navigation property
9 public virtual Department Department { get ; set ; }
10 }

How do you map inheritance in Entity Framework using TPH (Table-


Per-Hierarchy)?
In TPH (Table-Per-Hierarchy), all types in an inheritance hierarchy are stored in a single table,
with a discriminator column used to distinguish between types. This is the default inheritance
mapping strategy in Entity Framework.

Listing 8.53: Code example

1 // Example : TPH mapping in Entity Framework


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

8 public class Employee : Person


9 {
10 public decimal Salary { get ; set ; }

345
8.3. ORM Concepts and Entity Framework

11 }
12

13 public class Customer : Person


14 {
15 public string ContactDetails { get ; set ; }
16 }
17

18 // Entity Framework will map Employee and Customer to the same table with a
discriminator column

How do you map one-to-one relationships in Entity Framework?


One-to-one relationships in Entity Framework are defined by placing a foreign key in one entity
that refers to the primary key of another entity. You can enforce a one-to-one relationship using
the ‘HasRequired‘ and ‘WithOptional‘ methods in the ‘OnModelCreating‘ method.

Listing 8.54: Code example

1 // Example : One - to - one relationship in Entity Framework


2 public class Employee
3 {
4 public int EmployeeId { get ; set ; }
5 public string Name { get ; set ; }
6 public virtual EmployeeDetails EmployeeDetails { get ; set ; }
7 }
8

9 public class EmployeeDetails


10 {
11 public int EmployeeDetailsId { get ; set ; }
12 public string Address { get ; set ; }
13

14 // Foreign key
15 public int EmployeeId { get ; set ; }
16 public virtual Employee Employee { get ; set ; }
17 }

How do you define composite keys in Entity Framework?


Entity Framework does not support composite keys using ‘DataAnnotations‘. Instead, you must
configure composite keys using the Fluent API in the ‘OnModelCreating‘ method of your ‘DbCon-
text‘.

Listing 8.55: Code example

1 // Example : Defining composite keys using Fluent API


2 protected override void OnModelCreating ( ModelBuilder modelBuilder )

346
8.4. LINQ to Entities

3 {
4 modelBuilder . Entity < Employee >()
5 . HasKey ( e = > new { e . EmployeeId , e . DepartmentId }) ;
6 }

How does Entity Framework handle query translation, and what are some
common performance pitfalls?
Entity Framework translates LINQ queries into SQL, which is then executed on the database.
Common performance pitfalls include under-optimization of queries, over-reliance on lazy loading,
and performing operations that cannot be translated to SQL, resulting in in-memory execution.

Listing 8.56: Code example

1 // Example : Translating LINQ queries to SQL in Entity Framework


2 var employees = context . Employees
3 . Where ( e = > e . DepartmentId == 1)
4 . OrderBy ( e = > e . Name )
5 . ToList () ;

How do you map a stored procedure in Entity Framework, and when


would you use it?
Stored procedures can be mapped in Entity Framework for CRUD operations or more complex
logic that can’t be easily handled by LINQ queries. You can use the ‘FromSqlRaw‘ method to
execute stored procedures.

Listing 8.57: Code example

1 // Example : Mapping a stored procedure in Entity Framework


2 var employees = context . Employees
3 . FromSqlRaw ( " EXEC GetEmployeesByDepartment {0} " , departmentId )
4 . ToList () ;

8.4 LINQ to Entities


What is LINQ to Entities, and how does it differ from LINQ to SQL?
LINQ to Entities is a part of the Entity Framework and allows querying databases using LINQ
syntax, translating LINQ expressions into SQL queries. LINQ to SQL is designed specifically for
SQL Server, while LINQ to Entities supports multiple database providers and is more robust for
enterprise applications.

347
8.4. LINQ to Entities

Listing 8.58: Code example

1 // Example : Basic LINQ to Entities query


2 var employees = context . Employees
3 . Where ( e = > e . DepartmentId == 1)
4 . OrderBy ( e = > e . Name )
5 . ToList () ;

How does LINQ to Entities handle deferred execution, and why is it


important?
Deferred execution means that LINQ queries are not executed until the data is actually accessed,
allowing for query composition and optimization. This is important for performance as it prevents
unnecessary database calls until required.

Listing 8.59: Code example

1 // Example : Deferred execution in LINQ to Entities


2 var query = context . Employees . Where ( e = > e . DepartmentId == 1) ; // Query is not
executed yet
3 var employees = query . ToList () ; // Query is executed here

How do you join multiple tables using LINQ to Entities, and what is the
equivalent SQL query?
In LINQ to Entities, you can use ‘join‘ to perform inner joins between multiple tables. The LINQ
‘join‘ is equivalent to the SQL ‘INNER JOIN‘.

Listing 8.60: Code example

1 // Example : Joining two tables in LINQ to Entities


2 var query = from e in context . Employees
3 join d in context . Departments on e . DepartmentId equals d . DepartmentId
4 select new { e . Name , d . DepartmentName };
5

6 // Equivalent SQL : SELECT e . Name , d . DepartmentName FROM Employees e INNER JOIN


Departments d ON e . DepartmentId = d . DepartmentId ;

What is the difference between ‘First()‘, ‘FirstOrDefault()‘, ‘Single()‘,


and ‘SingleOrDefault()‘ in LINQ to Entities?
• First() returns the first matching element and throws an exception if no match is found.
• FirstOrDefault() returns the first matching element or the default value if no match is
found.

348
8.4. LINQ to Entities

• Single() returns the single matching element and throws an exception if there are multiple
or no matches.
• SingleOrDefault() returns the single matching element or the default value if no match is
found, but throws an exception if there are multiple matches.

Listing 8.61: Code example

1 // Example : Using FirstOrDefault in LINQ to Entities


2 var employee = context . Employees . FirstOrDefault ( e = > e . EmployeeId == 1) ;

How does LINQ to Entities handle projection with the ‘Select‘ clause,
and what is it used for?
The ‘Select‘ clause in LINQ to Entities is used to project (transform) data into a new shape or
form, such as selecting specific columns or creating a new object type. It is equivalent to the SQL
‘SELECT‘ clause.

Listing 8.62: Code example

1 // Example : Using Select for projection in LINQ to Entities


2 var query = context . Employees
3 . Select ( e = > new { e . EmployeeId , e . Name }) ;

How do you perform grouping and aggregation in LINQ to Entities?


You can use the ‘GroupBy‘ method in LINQ to Entities to group data based on a key, followed
by aggregation functions like ‘Count()‘, ‘Sum()‘, ‘Average()‘, etc., to perform calculations on each
group.

Listing 8.63: Code example

1 // Example : Grouping employees by department and counting them


2 var query = context . Employees
3 . GroupBy ( e = > e . DepartmentId )
4 . Select ( g = > new { DepartmentId = g . Key , EmployeeCount = g . Count () }) ;

How does LINQ to Entities optimize performance for queries with large
datasets?
LINQ to Entities optimizes performance by translating LINQ expressions into SQL queries ex-
ecuted at the database level. It minimizes data loading into memory and reduces the number
of round-trips to the database through techniques like deferred execution, pagination, and lazy
loading.

349
8.4. LINQ to Entities

Listing 8.64: Code example

1 // Example : Using pagination to limit results in LINQ to Entities


2 var query = context . Employees
3 . OrderBy ( e = > e . EmployeeId )
4 . Skip (10)
5 . Take (10) ;

How does the ‘Include‘ and ‘ThenInclude‘ methods work in LINQ to


Entities, and what problem do they solve?
The ‘Include‘ method is used to eagerly load related entities in LINQ to Entities, solving the
**N+1 query problem**, where multiple database queries are generated for related entities. By
using ‘Include‘, all related data is fetched in a single query, reducing database round-trips.
The ‘ThenInclude‘ method is used to load nested related entities. It allows you to specify
additional levels of navigation properties for eager loading when working with relationships beyond
the first level.
How They Work:
• Include: Used for loading a single level of related entities.
• ThenInclude: Used for loading subsequent levels of related entities.

Listing 8.65: Code example

1 // Example : Eagerly loading related entities with Include and ThenInclude


2

3 // Loading Employees with their Departments


4 var employees = context . Employees
5 . Include ( e = > e . Department )
6 . ToList () ;
7

8 // Loading Employees with their Departments and the Company related to the
Department
9 var employeesWithDetails = context . Employees
10 . Include ( e = > e . Department )
11 . ThenInclude ( d = > d . Company )
12 . ToList () ;

SQL Generated: For the ‘ThenInclude‘ example, a single query is generated, such as:

Listing 8.66: Code example

1 SELECT e .* , d .* , c .*
2 FROM Employees e
3 LEFT JOIN Departments d ON e . DepartmentId = d . Id
4 LEFT JOIN Companies c ON d . CompanyId = c . Id ;

Problem Solved: By using ‘Include‘ and ‘ThenInclude‘, you:

350
8.4. LINQ to Entities

• Reduce the number of queries to the database.


• Fetch all necessary related data in a single query.
• Prevent the **N+1 query problem**, where fetching related entities for multiple parent
entities causes a large number of queries.

What is the difference between ‘AsQueryable()‘ and ‘AsEnumerable()‘ in


LINQ to Entities?
‘AsQueryable()‘ allows further LINQ expressions to be translated to SQL and executed at the
database level, whereas ‘AsEnumerable()‘ forces the query to be executed and brings the data into
memory, after which further LINQ expressions are applied in-memory.

Listing 8.67: Code example

1 // Example : Using AsQueryable for database - level execution


2 var query = context . Employees . AsQueryable ()
3 . Where ( e = > e . Name . StartsWith ( " A " ) ) ; // Translated to SQL
4

5 // Example : Using AsEnumerable for in - memory execution


6 var employees = context . Employees . AsEnumerable ()
7 . Where ( e = > e . Name . StartsWith ( " A " ) ) ; // Executed in - memory

How do you use LINQ to Entities to perform a LEFT OUTER JOIN?


A ‘LEFT OUTER JOIN‘ can be performed in LINQ to Entities using the ‘DefaultIfEmpty()‘
method, which provides a default value when there is no match, simulating a ‘LEFT JOIN‘ in
SQL.

Listing 8.68: Code example

1 // Example : Performing a LEFT OUTER JOIN in LINQ to Entities


2 var query = from e in context . Employees
3 join d in context . Departments on e . DepartmentId equals d . DepartmentId
into deptGroup
4 from d in deptGroup . DefaultIfEmpty () // Left join
5 select new { e . Name , DepartmentName = d == null ? " No Department " : d .
DepartmentName };

How do you perform a subquery in LINQ to Entities?


In LINQ to Entities, subqueries are performed by embedding one query inside another. The
subquery is often used to filter or aggregate data before applying the main query logic.

Listing 8.69: Code example

351
8.4. LINQ to Entities

1 // Example : Subquery in LINQ to Entities


2 var query = context . Employees
3 . Where ( e = > context . Orders
4 . Any ( o = > o . EmployeeId == e . EmployeeId && o . OrderTotal > 1000) ) ;

How do you perform sorting and ordering in LINQ to Entities?


Sorting in LINQ to Entities is performed using the ‘OrderBy()‘, ‘OrderByDescending()‘,
‘ThenBy()‘, and ‘ThenByDescending()‘ methods. These methods allow sorting by one or more
fields in ascending or descending order.
Listing 8.70: Code example

1 // Example : Sorting and ordering in LINQ to Entities


2 var query = context . Employees
3 . OrderBy ( e = > e . DepartmentId )
4 . ThenBy ( e = > e . Name ) ;

How do you perform a ‘GroupJoin‘ in LINQ to Entities, and how is it


different from a regular ‘Join‘ ?
‘GroupJoin‘ is used to group results from a join operation, rather than flattening the result as a
regular ‘Join‘ does. It is useful when you want to keep the grouping intact after performing the
join.
Listing 8.71: Code example

1 // Example : GroupJoin in LINQ to Entities


2 var query = from d in context . Departments
3 join e in context . Employees on d . DepartmentId equals e . DepartmentId
into employeeGroup
4 select new { d . DepartmentName , Employees = employeeGroup };

How does LINQ to Entities handle anonymous types, and why are they
useful?
Anonymous types in LINQ to Entities allow you to create new object types on the fly without
explicitly defining a class. They are useful when you need to project data into a custom shape or
form for specific queries.
Listing 8.72: Code example

1 // Example : Using anonymous types in LINQ to Entities


2 var query = context . Employees
3 . Select ( e = > new { e . EmployeeId , FullName = e . FirstName + " " + e . LastName }) ;

352
8.4. LINQ to Entities

How do you write queries with multiple ‘Where‘ conditions in LINQ to


Entities, and how does it affect query performance?
You can chain multiple ‘Where‘ conditions in LINQ to Entities to filter data. These conditions
are combined using ‘AND‘ logic in SQL, and LINQ to Entities optimizes them into a single SQL
query.
Listing 8.73: Code example

1 // Example : Multiple Where conditions in LINQ to Entities


2 var query = context . Employees
3 . Where ( e = > e . DepartmentId == 1)
4 . Where ( e = > e . Salary > 50000) ;

How does LINQ to Entities handle null values, and how can you prevent
null reference exceptions in your queries?
LINQ to Entities translates null checks to SQL, allowing you to safely handle null values in the
database. You can prevent null reference exceptions by using the null-coalescing operator (‘??‘) or
null-conditional operators (‘?.‘).
Listing 8.74: Code example

1 // Example : Handling null values in LINQ to Entities


2 var query = context . Employees
3 . Select ( e = > new { Name = e . Name ?? " Unknown " , e . Salary }) ;

How do you execute a stored procedure using LINQ to Entities?


LINQ to Entities supports calling stored procedures using the ‘FromSqlRaw‘ method or by mapping
the procedure to a function in your context.
Listing 8.75: Code example

1 // Example : Executing a stored procedure in LINQ to Entities


2 var employees = context . Employees
3 . FromSqlRaw ( " EXEC GetEmployeesByDepartment {0} " , departmentId )
4 . ToList () ;

What are compiled queries in LINQ to Entities, and how can they im-
prove performance?
Compiled queries are precompiled LINQ queries that can be reused multiple times, avoiding the
overhead of query translation. They improve performance by caching the execution plan for a
given query structure.

353
8.4. LINQ to Entities

Listing 8.76: Code example

1 // Example : Compiled query in LINQ to Entities


2 public static readonly Func < MyDbContext , int , IEnumerable < Employee > >
GetEmployeesByDepartment =
3 CompiledQuery . Compile (( MyDbContext context , int departmentId ) = >
4 context . Employees . Where ( e = > e . DepartmentId == departmentId ) ) ;
5

6 var employees = GetEmployeesByDepartment ( context , 1) ;

How does LINQ to Entities handle asynchronous queries, and why is it


beneficial to use async methods?
LINQ to Entities provides asynchronous versions of query methods (e.g., ‘ToListAsync()‘,
‘FirstOrDefaultAsync()‘) to avoid blocking the calling thread. Using async queries improves scal-
ability and responsiveness, especially in web applications.

Listing 8.77: Code example

1 // Example : Using asynchronous query methods in LINQ to Entities


2 var employees = await context . Employees . ToListAsync () ;

Can Entity Framework Have Multiple DbContext?


Yes, Entity Framework (EF) supports multiple ‘DbContext‘ instances in the same application.
Each ‘DbContext‘ can represent a different database or a subset of tables within the same database,
allowing you to organize and manage your application’s data access layer more effectively.
Use Cases for Multiple DbContext:
• Multiple Databases: Each ‘DbContext‘ can be configured to point to a different database.
• Separation of Concerns: Split your application’s data models into different ‘DbContext‘
classes for better modularity and maintainability (e.g., ‘UserDbContext‘ for user manage-
ment, ‘OrderDbContext‘ for order processing).
• Microservices: In microservices architectures, each service might have its own ‘DbContext‘
tied to its specific domain.

Listing 8.78: Code example

1 using System ;
2 using System . Data . Entity ;
3

4 public class UserDbContext : DbContext


5 {
6 public DbSet < User > Users { get ; set ; }
7 }
8

354
8.4. LINQ to Entities

9 public class OrderDbContext : DbContext


10 {
11 public DbSet < Order > Orders { get ; set ; }
12 }
13

14 // Example usage
15 class Program
16 {
17 static void Main ()
18 {
19 using ( var userContext = new UserDbContext () )
20 {
21 var user = new User { Name = " John Doe " };
22 userContext . Users . Add ( user ) ;
23 userContext . SaveChanges () ;
24 }
25

26 using ( var orderContext = new OrderDbContext () )


27 {
28 var order = new Order { ProductName = " Laptop " , Quantity = 1 };
29 orderContext . Orders . Add ( order ) ;
30 orderContext . SaveChanges () ;
31 }
32 }
33 }
34

35 public class User


36 {
37 public int Id { get ; set ; }
38 public string Name { get ; set ; }
39 }
40

41 public class Order


42 {
43 public int Id { get ; set ; }
44 public string ProductName { get ; set ; }
45 public int Quantity { get ; set ; }
46 }

355
8.5. Advanced Topics in Entity Framework and LINQ

8.5 Advanced Topics in Entity Framework and LINQ


How does Entity Framework handle disconnected entities, and what chal-
lenges do they present?
Disconnected entities occur when entities are no longer tracked by the ‘DbContext‘, such as when
they are passed between layers (e.g., in web applications). Reattaching these entities to a new
context and ensuring correct change tracking can be challenging. The ‘Attach‘ and ‘Update‘
methods are used to handle such entities, but care must be taken to avoid unintended updates.

Listing 8.79: Code example

1 // Example : Handling a disconnected entity


2 var employee = new Employee { EmployeeId = 1 , Name = " Updated Name " }; //
Disconnected entity
3 context . Employees . Attach ( employee ) ; // Reattach to context
4 context . Entry ( employee ) . State = EntityState . Modified ; // Mark as modified
5 context . SaveChanges () ; // Apply the changes to the database

How does Entity Framework implement inheritance strategies, and what


is the difference between TPH, TPT, and TPC?
Entity Framework supports three inheritance strategies:
• ‘TPH‘ (Table Per Hierarchy): All types in the hierarchy are stored in a single table with
a discriminator column.
• ‘TPT‘ (Table Per Type): Each type in the hierarchy has its own table.
• ‘TPC‘ (Table Per Concrete Class): Each concrete type in the hierarchy has its own
table without any shared base class table.

Listing 8.80: Code example

1 // Example : Configuring TPH inheritance in Entity Framework


2 modelBuilder . Entity < Person >()
3 . HasDiscriminator < string >( " PersonType " )
4 . HasValue < Employee >( " Employee " )
5 . HasValue < Customer >( " Customer " ) ;

What are global query filters in Entity Framework, and how can they be
used?
Global query filters allow you to apply a filter to all queries for a specific entity type. This is
useful for implementing soft deletes, multi-tenancy, or other scenarios where a filter should be
consistently applied.

356
8.5. Advanced Topics in Entity Framework and LINQ

Listing 8.81: Code example

1 // Example : Using global query filters for soft delete


2 modelBuilder . Entity < Employee >() . HasQueryFilter ( e = > ! e . IsDeleted ) ;

How does Entity Framework Core handle concurrency conflicts, and what
strategies can be used to resolve them?
Entity Framework Core uses optimistic concurrency by default, where a ‘RowVersion‘ column or
similar mechanism is used to detect conflicts. When a conflict occurs, the developer can choose to
overwrite, discard, or merge changes manually.

Listing 8.82: Code example

1 // Example : Handling concurrency conflict


2 try
3 {
4 context . SaveChanges () ;
5 }
6 catch ( DbUpdateConcurrencyException ex )
7 {
8 // Resolve the conflict by keeping client changes
9 foreach ( var entry in ex . Entries )
10 {
11 var currentValues = entry . CurrentValues ;
12 var databaseValues = entry . GetDatabaseValues () ;
13 entry . OriginalValues . SetValues ( databaseValues ) ; // Keep database values
14 }
15 context . SaveChanges () ; // Retry saving changes
16 }

How can you create a custom convention in Entity Framework?


Custom conventions in Entity Framework allow you to apply specific rules across your model
without explicitly configuring each entity. You can create custom conventions by overriding the
‘OnModelCreating‘ method in your ‘DbContext‘.

Listing 8.83: Code example

1 // Example : Custom convention for string properties to set max length


2 protected override void OnModelCreating ( ModelBuilder modelBuilder )
3 {
4 foreach ( var entity in modelBuilder . Model . GetEntityTypes () )
5 {
6 var properties = entity . ClrType . GetProperties ()
7 . Where ( p = > p . PropertyType == typeof ( string ) ) ;

357
8.5. Advanced Topics in Entity Framework and LINQ

9 foreach ( var property in properties )


10 {
11 modelBuilder . Entity ( entity . Name )
12 . Property ( property . Name )
13 . HasMaxLength (100) ;
14 }
15 }
16 }

What are LINQ expression trees, and how are they used in LINQ to
Entities?
LINQ expression trees represent code in a tree-like data structure, where each node is an expression
(e.g., method call, binary operation). In LINQ to Entities, expression trees are used to translate
LINQ queries into SQL queries for execution in the database.
Listing 8.84: Code example

1 // Example : Creating and using an expression tree in LINQ to Entities


2 Expression < Func < Employee , bool > > filter = e = > e . Salary > 50000;
3 var employees = context . Employees . Where ( filter ) . ToList () ;

How do you handle raw SQL commands in Entity Framework while still
benefiting from tracking and relationships?
You can execute raw SQL commands in Entity Framework using ‘FromSqlRaw‘ for queries or
‘ExecuteSqlRaw‘ for non-query operations. When using ‘FromSqlRaw‘, EF can still track the
entities returned if they are part of your model.
Listing 8.85: Code example

1 // Example : Executing raw SQL while preserving tracking in Entity Framework


2 var employees = context . Employees
3 . FromSqlRaw ( " SELECT * FROM Employees WHERE DepartmentId = {0} " , departmentId )
4 . ToList () ;

How does Entity Framework handle batch updates, and what are the
limitations?
Entity Framework does not natively support batch updates (i.e., updating multiple rows in a
single statement) without third-party extensions. By default, EF sends one ‘UPDATE‘ statement
per entity. Extensions like ‘EFCore.BulkExtensions‘ or ‘Z.EntityFramework.Extensions‘ can help
overcome this limitation.

358
8.5. Advanced Topics in Entity Framework and LINQ

Listing 8.86: Code example

1 // Example : Using a third - party extension for batch updates


2 context . Employees
3 . Where ( e = > e . DepartmentId == 1)
4 . BatchUpdate ( e = > new Employee { Salary = e . Salary + 1000 }) ;

What is projection in LINQ, and how does it affect performance in LINQ


to Entities?
Projection in LINQ refers to transforming the result of a query into a new shape or form using
the ‘Select‘ method. In LINQ to Entities, projection can improve performance by reducing the
amount of data fetched from the database.

Listing 8.87: Code example

1 // Example : Projecting specific columns to improve performance


2 var employeeNames = context . Employees . Select ( e = > new { e . EmployeeId , e . Name }) .
ToList () ;

How does Entity Framework handle query caching, and how can you
control caching behavior?
Entity Framework caches query plans but not query results by default. Query plan caching helps
avoid recompiling queries on subsequent executions. You can control caching behavior by using
compiled queries or adjusting the behavior at the application level.

Listing 8.88: Code example

1 // Example : Using a compiled query for caching query plans


2 public static readonly Func < MyDbContext , int , IEnumerable < Employee > >
GetEmployeesByDeptId =
3 CompiledQuery . Compile (( MyDbContext context , int departmentId ) = >
4 context . Employees . Where ( e = > e . DepartmentId == departmentId ) ) ;

What is the purpose of the ‘DbContextFactory‘ pattern, and when should


you use it?
The ‘DbContextFactory‘ pattern is used to create new instances of ‘DbContext‘ when working
in environments like desktop applications or multi-threaded scenarios where a single instance of
‘DbContext‘ may not be safe or practical to share.

Listing 8.89: Code example

1 // Example : Implementing DbContextFactory pattern

359
8.5. Advanced Topics in Entity Framework and LINQ

2 public class DbContextFactory


3 {
4 public MyDbContext CreateDbContext ()
5 {
6 var optionsBuilder = new DbContextOptionsBuilder < MyDbContext >() ;
7 optionsBuilder . UseSqlServer ( " connection - string " ) ;
8 return new MyDbContext ( optionsBuilder . Options ) ;
9 }
10 }

How do you implement soft deletes using Entity Framework, and what
are the pros and cons of this approach?
Soft deletes are implemented by adding a ‘IsDeleted‘ flag to entities and modifying queries to
exclude deleted records. This allows you to "delete" entities without physically removing them
from the database, preserving data for auditing or recovery.

Listing 8.90: Code example

1 // Example : Implementing soft deletes with Entity Framework


2 public class Employee
3 {
4 public int EmployeeId { get ; set ; }
5 public string Name { get ; set ; }
6 public bool IsDeleted { get ; set ; } // Soft delete flag
7 }
8

9 // Applying global query filter to exclude soft - deleted records


10 modelBuilder . Entity < Employee >() . HasQueryFilter ( e = > ! e . IsDeleted ) ;

How can you create and use database views with Entity Framework?
Entity Framework can map database views to entities by treating the view as a read-only table.
You can use ‘HasNoKey()‘ in Fluent API to configure the view since views typically do not have
primary keys.

Listing 8.91: Code example

1 // Example : Mapping a database view in Entity Framework


2 modelBuilder . Entity < EmployeeView >() . HasNoKey () ; // Mapping a view
3

4 public class EmployeeView


5 {
6 public int EmployeeId { get ; set ; }
7 public string Name { get ; set ; }
8 public string DepartmentName { get ; set ; }

360
8.5. Advanced Topics in Entity Framework and LINQ

9 }

What are the differences between ‘SaveChanges()‘ and ‘SaveChange-


sAsync()‘ in Entity Framework?
‘SaveChanges()‘ is a synchronous method that blocks the calling thread until the operation is
complete. ‘SaveChangesAsync()‘ is the asynchronous version and allows non-blocking operations,
improving responsiveness and scalability, especially in web applications.

Listing 8.92: Code example

1 // Example : Using SaveChangesAsync in Entity Framework


2 await context . SaveChangesAsync () ; // Non - blocking save operation

How do you handle complex queries involving subqueries or multiple joins


in LINQ to Entities?
Complex queries in LINQ to Entities are handled using a combination of ‘joins‘, ‘subqueries‘, and
‘grouping‘ to perform multi-table operations. LINQ’s flexibility allows composing complex SQL
queries directly in C#.

Listing 8.93: Code example

1 // Example : Complex query with subqueries and joins


2 var query = from e in context . Employees
3 join d in context . Departments on e . DepartmentId equals d . DepartmentId
4 where ( from o in context . Orders
5 where o . EmployeeId == e . EmployeeId
6 select o ) . Any ()
7 select new { e . Name , d . DepartmentName };

How do you handle database transactions in Entity Framework to ensure


consistency across multiple operations?
Entity Framework supports database transactions using the ‘TransactionScope‘ class or the ‘Db-
Context.Database.BeginTransaction()‘ method. Transactions ensure that multiple operations are
treated as a single unit of work and either all succeed or none.

Listing 8.94: Code example

1 // Example : Using transactions in Entity Framework


2 using ( var transaction = context . Database . BeginTransaction () )
3 {
4 try

361
8.5. Advanced Topics in Entity Framework and LINQ

5 {
6 context . Employees . Add ( new Employee { Name = " John " }) ;
7 context . SaveChanges () ;
8

9 context . Orders . Add ( new Order { EmployeeId = 1 , OrderTotal = 100 }) ;


10 context . SaveChanges () ;
11

12 transaction . Commit () ; // Commit transaction if successful


13 }
14 catch
15 {
16 transaction . Rollback () ; // Rollback transaction in case of failure
17 }
18 }

How does Entity Framework handle ‘Include()‘ with multiple levels of


navigation properties, and what are the performance implications?
Entity Framework allows you to eagerly load multiple levels of related data using the ‘Then-
Include()‘ method. However, using multiple ‘Include()‘ calls can result in large, complex SQL
queries that impact performance, especially with large datasets.

Listing 8.95: Code example

1 // Example : Multiple levels of Include and ThenInclude in Entity Framework


2 var employees = context . Employees
3 . Include ( e = > e . Department )
4 . ThenInclude ( d = > d . Location )
5 . ToList () ;

What is the role of ‘IQueryable‘ in LINQ to Entities, and how does it


differ from ‘IEnumerable‘ ?
‘IQueryable‘ is used for queries that are translated into SQL and executed at the database level.
‘IEnumerable‘ executes the query in-memory, which can be less efficient for large datasets.
‘IQueryable‘ allows for deferred execution and database-side query optimization.

Listing 8.96: Code example

1 // Example : IQueryable vs IEnumerable in LINQ to Entities


2 IQueryable < Employee > query = context . Employees . Where ( e = > e . Salary > 50000) ; //
Queryable , deferred execution
3 IEnumerable < Employee > employees = query . ToList () ; // Enumerable , executed in -
memory

362
9 | Advanced C# Concepts

Advanced C# concepts push beyond the basics and empower developers to write more expressive,
efficient, and maintainable code. Topics such as delegates, events, generics, LINQ, asynchronous
programming, and reflection open the door to building more dynamic and responsive applications.

In technical interviews, these advanced features often surface in both practical coding exercises
and conceptual discussions. Employers value candidates who can apply these tools appropriately,
as they indicate a deeper command of the language and the ability to solve complex problems
using elegant solutions.

Mastering advanced C# concepts not only enhances your day-to-day coding but also sets
you apart in the hiring process. It demonstrates that you’re not just comfortable with the lan-
guage—you can leverage its full power to build clean, scalable, and modern applications.

9.1 Advanced C# Language Features


Are nulls something to worry about when coding a database call? How
do you handle them?
Yes, nulls are something to be mindful of when coding a database call. Nulls represent missing or
undefined data, and if not handled correctly, they can lead to runtime errors, incorrect logic, or
unexpected behavior in your application. Ensuring proper handling of nulls is essential for building
robust and reliable applications.

Why Nulls Are a Concern:


• Runtime Errors: Accessing a null value can cause exceptions like ‘NullReferenceException‘
in C# or similar errors in other languages.
• Data Integrity Issues: Nulls can impact query results, especially with aggregate functions
or joins.

363
9.1. Advanced C# Language Features

• Logic Confusion: Null comparisons may lead to unintended outcomes because nulls are
not "equal" to any value, even another null.

How to Handle Nulls:


• Database Design: Use ‘NOT NULL‘ constraints where appropriate to enforce required
fields. Provide default values for columns to avoid nulls where possible.
• Application Logic: Check for nulls explicitly before using values in the application. Use
null-coalescing operators or defaults to handle nulls gracefully.
• Query-Level Handling: Use SQL functions like ‘ISNULL()‘ or ‘COALESCE()‘ to replace
nulls with default values in query results.

Listing 9.1: Handling Nulls in a Database Call (C# with Entity Framework)

1 public string GetCustomerName ( int customerId )


2 {
3 var customer = _dbContext . Customers . FirstOrDefault ( c = > c . Id == customerId ) ;
4

5 // Handle potential null customer


6 return customer ?. Name ?? " Unknown Customer " ;
7 }

Listing 9.2: Handling Nulls in SQL

1 SELECT
2 CustomerId ,
3 ISNULL ( CustomerName , ’ Unknown ’) AS CustomerName
4 FROM Customers ;

Key Practices:
• Validate Inputs: Ensure database calls do not send invalid or null parameters.
• Guard Against Nulls: Always check for null values when accessing database call results.
• Enforce Constraints: Use database constraints to minimize null occurrences where possi-
ble.
Summary: Nulls are a common challenge when working with database calls, but with proper
validation, default handling, and thoughtful database design, they can be effectively managed to
ensure robust application behavior and accurate data processing.

What is the difference between TRUNCATE and DELETE in SQL?


Both ‘TRUNCATE‘ and ‘DELETE‘ are SQL commands used to remove records from a table, but
they differ significantly in behavior, performance, and use cases.

364
9.1. Advanced C# Language Features

Differences Between TRUNCATE and DELETE:


• Operation Type: TRUNCATE: Removes all rows from a table without logging individual
row deletions, making it a DDL (Data Definition Language) operation. DELETE:
Removes rows based on a condition or all rows if no condition is provided. It is a DML
(Data Manipulation Language) operation.
• Performance: TRUNCATE: Faster because it does not generate individual row deletion
logs and resets the table’s identity seed. DELETE: Slower as it logs each deleted row and
maintains transaction logs for rollback.
• Conditions: TRUNCATE: Does not allow filtering; it always removes all rows from the
table. DELETE: Allows filtering with a ‘WHERE‘ clause to remove specific rows.
• Triggers: TRUNCATE: Does not activate ‘AFTER DELETE‘ triggers. DELETE: Activates
‘AFTER DELETE‘ triggers if defined.
• Rollback: TRUNCATE: Can be rolled back if used within a transaction. DELETE: Always
supports rollback as it is fully logged.
• Usage Restrictions: TRUNCATE: Cannot be used on tables with foreign key constraints.
DELETE: Can be used on tables with foreign keys but requires proper cascading or constraint
handling.

Listing 9.3: SQL TRUNCATE and DELETE

1 -- TRUNCATE Example
2 TRUNCATE TABLE Employees ; -- Removes all rows and resets the identity column
3

4 -- DELETE Example
5 DELETE FROM Employees WHERE Department = ’ HR ’; -- Deletes only rows from the HR
department
6

7 -- DELETE All Rows Example


8 DELETE FROM Employees ; -- Removes all rows but keeps the identity column unchanged

Summary: Use ‘TRUNCATE‘ when you need to quickly remove all rows from a table and
reset its identity, typically for temporary or staging tables. Use ‘DELETE‘ when you need granular
control over row deletion or when triggers and logging are required.

What does the Entity Framework bring to the table, and will it improve
your designs (why)?
Entity Framework (EF) is an Object-Relational Mapping (ORM) tool for .NET that simplifies
database interactions by abstracting the data access layer. It allows developers to work with
database objects using strongly-typed C# classes rather than raw SQL queries.

365
9.1. Advanced C# Language Features

What Entity Framework Brings to the Table:


• Abstraction of Database Operations: Eliminates the need for most raw SQL, reducing
boilerplate code.
• LINQ Integration: Enables querying data using LINQ, making queries more readable and
type-safe.
• Change Tracking: Automatically tracks changes to objects and generates SQL for database
updates.
• Migration Support: Simplifies schema evolution with built-in migration tools to version
and update databases.
• Code-First and Database-First Approaches: Offers flexibility to design the database
schema in code or derive it from an existing database.
• Cross-Database Compatibility: Supports multiple database providers (e.g., SQL Server,
PostgreSQL, MySQL).

How Entity Framework Improves Designs:


• Improved Maintainability: Abstracting SQL logic into models and DbContext reduces
the complexity of data access code.
• Better Testability: Mockable DbContext and repositories make it easier to test the data
layer independently.
• Consistency: Enforces a unified way to interact with databases across the application.
• Rapid Development: Speeds up development by automating many data access tasks.
Listing 9.4: Code example

1 public class Product


2 {
3 public int Id { get ; set ; }
4 public string Name { get ; set ; }
5 public decimal Price { get ; set ; }
6 }
7

8 public class AppDbContext : DbContext


9 {
10 public DbSet < Product > Products { get ; set ; }
11 }
12

13 public class ProductRepository


14 {
15 private readonly AppDbContext _context ;
16

17 public ProductRepository ( AppDbContext context )


18 {
19 _context = context ;

366
9.1. Advanced C# Language Features

20 }
21

22 public async Task < IEnumerable < Product > > GetAllProductsAsync ()
23 {
24 return await _context . Products . ToListAsync () ;
25 }
26

27 public async Task AddProductAsync ( Product product )


28 {
29 await _context . Products . AddAsync ( product ) ;
30 await _context . SaveChangesAsync () ;
31 }
32 }

Summary: Entity Framework improves designs by enhancing productivity, maintainability,


and scalability. It allows developers to focus on business logic rather than low-level database
operations while ensuring type safety and consistency across the application.

Why would you choose to use an object-relational database model ap-


proach?
An object-relational database (ORDBMS) approach combines the relational database model with
object-oriented principles. It is particularly useful in scenarios where you need the robust querying
capabilities of relational databases alongside the flexibility of object-oriented programming.

Reasons to Choose an Object-Relational Database Model:


• Complex Data Representation: Supports advanced data types (e.g., arrays, JSON, cus-
tom types) for modeling complex real-world entities.
• Seamless Integration with OOP: Maps relational data directly to object-oriented struc-
tures, reducing the impedance mismatch between application code and database.
• Powerful Querying: Leverages SQL’s robust querying capabilities while handling complex
data relationships.
• Performance Optimization: Provides indexes, constraints, and transactions for efficient
and consistent data operations.
• Flexibility: Allows extending relational models with object-like features such as inheritance
and custom methods.

Listing 9.5: Code example

1 public class Product


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

367
9.1. Advanced C# Language Features

5 public decimal Price { get ; set ; }


6 public List < string > Tags { get ; set ; } // Complex data type
7 }
8

9 public class AppDbContext : DbContext


10 {
11 public DbSet < Product > Products { get ; set ; }
12

13 protected override void OnModelCreating ( ModelBuilder modelBuilder )


14 {
15 modelBuilder . Entity < Product >()
16 . Property ( p = > p . Tags )
17 . HasConversion (
18 v = > string . Join ( ’ , ’ , v ) ,
19 v = > v . Split ( ’ , ’ , StringSplitOptions . RemoveEmptyEntries ) . ToList () )
;
20 }
21 }

Summary: Using an object-relational model is beneficial when you require the relational
database’s reliability and query power while efficiently handling complex data and aligning it with
object-oriented application design.

How could you use Dapper to improve your designs?


Dapper is a lightweight micro-ORM for .NET that improves database interaction by combining
the flexibility of raw SQL with object mapping. It enhances designs by providing a fast, simple,
and maintainable data access layer.

Benefits:
• Simplicity: Direct SQL interaction for better control and fewer abstractions.
• Performance: Minimal overhead with fast query-to-object mapping.
• Strong Typing: Ensures type safety with strongly-typed models.
• Flexibility: Supports raw SQL, stored procedures, and complex queries.
• Maintainability: Reduces boilerplate with clean and modular repository patterns.

Listing 9.6: Code example

1 public class ProductRepository


2 {
3 private readonly string _connectionString ;
4

5 public ProductRepository ( string connectionString )


6 {
7 _connectionString = connectionString ;

368
9.1. Advanced C# Language Features

8 }
9

10 public async Task < Product > GetProductByIdAsync ( int id )


11 {
12 using ( var db = new SqlConnection ( _connectionString ) )
13 {
14 string query = " SELECT Id , Name , Price FROM Products WHERE Id = @Id " ;
15 return await db . QueryFirstOrDefaultAsync < Product >( query , new { Id = id
}) ;
16 }
17 }
18 }
19

20 // Product Model
21 public class Product
22 {
23 public int Id { get ; set ; }
24 public string Name { get ; set ; }
25 public decimal Price { get ; set ; }
26 }

Summary: Dapper improves performance, maintains simplicity, and fits seamlessly into mod-
ular designs like repository patterns while offering better control over database interactions.

Should code directly call a production database table, and if so, what
best practices should be followed?
Code can directly interact with a production database table if required, but it must follow strict
best practices to ensure safety, performance, and maintainability. Direct database calls are com-
mon in scenarios such as data access layers, reporting tools, or when working with repositories.
However, they should be handled cautiously to avoid issues like performance degradation, security
vulnerabilities, or accidental data corruption.

Best Practices for Direct Database Calls:


• Use Parameterized Queries: Always use parameterized queries or an ORM to prevent
SQL injection attacks.
• Follow the Principle of Least Privilege: Ensure that the database user making the call
has minimal permissions, limited to only the required operations.
• Abstract Database Access: Encapsulate direct database calls in a repository or data
access layer to maintain separation of concerns.
• Avoid Hardcoding Queries: Store queries in a centralized location or use ORM-generated
queries to ensure consistency and maintainability.

369
9.1. Advanced C# Language Features

• Implement Error Handling: Handle database errors gracefully to prevent application


crashes.
• Validate and Sanitize Inputs: Ensure all user inputs are validated and sanitized before
being included in database queries.
• Optimize Queries: Use indexes, avoid SELECT *, and limit the number of rows returned
for better performance.
• Monitor and Log: Log database calls for auditability and monitor query performance in
production.
• Avoid Long-Lived Connections: Use connection pooling to efficiently manage database
connections.
• Test Queries in Staging: Run and validate queries in a staging environment before de-
ploying them to production.

Listing 9.7: Code example

1 using System ;
2 using System . Data . SqlClient ;
3

4 public class ProductRepository


5 {
6 private readonly string _connectionString ;
7

8 public ProductRepository ( string connectionString )


9 {
10 _connectionString = connectionString ;
11 }
12

13 public Product GetProductById ( int id )


14 {
15 // Using parameterized query to prevent SQL injection
16 string query = " SELECT Id , Name , Price FROM Products WHERE Id = @Id " ;
17

18 using ( var connection = new SqlConnection ( _connectionString ) )


19 using ( var command = new SqlCommand ( query , connection ) )
20 {
21 command . Parameters . AddWithValue ( " @Id " , id ) ;
22 connection . Open () ;
23

24 using ( var reader = command . ExecuteReader () )


25 {
26 if ( reader . Read () )
27 {
28 return new Product
29 {
30 Id = reader . GetInt32 (0) ,
31 Name = reader . GetString (1) ,

370
9.1. Advanced C# Language Features

32 Price = reader . GetDecimal (2)


33 };
34 }
35 }
36 }
37

38 return null ; // Product not found


39 }
40 }

Key Takeaways:
• Should You Directly Call a Production Database Table? Yes, but only when necessary and
with safeguards in place.
• Best Practices: Follow security protocols, abstract access, optimize performance, and validate
inputs.
• Alternative Approaches: Use stored procedures, ORM tools like Entity Framework, or
database views for added abstraction and security.
By adhering to these best practices, you can safely and efficiently make direct calls to production
database tables while minimizing risks.

What is an example of big picture thinking when reviewing code?


Big picture thinking when reviewing code involves evaluating the code beyond its immediate im-
plementation details to ensure it aligns with broader goals, such as maintainability, scalability,
and overall system architecture. It requires considering how the code fits into the application as a
whole and its impact on future development.

What Big Picture Thinking Involves:


• Alignment with Architecture: Ensures the code follows established architectural patterns
and principles.
• Future Scalability: Considers whether the code will support scaling requirements in terms
of both performance and functionality.
• Cross-Module Dependencies: Evaluates how the code interacts with other parts of the
system to avoid tight coupling or unintended side effects.
• Maintainability: Reviews whether the code is clear, well-documented, and easy to modify
or debug.
• Consistency: Checks adherence to coding standards and best practices across the project.

371
9.1. Advanced C# Language Features

Key Practices:
• Understand the Context: Review the code with a clear understanding of the system’s
goals and constraints.
• Ask Questions: Does this code handle edge cases? Will this design support potential
changes in requirements?
• Evaluate Trade-offs: Balance between short-term implementation efficiency and long-term
project sustainability.

Listing 9.8: Code example

1 public List < Order > GetOrdersByCustomerId ( int customerId )


2 {
3 // Potential issue : Is this query efficient for large datasets ?
4 return _dbContext . Orders . Where ( o = > o . CustomerId == customerId ) . ToList () ;
5 }

Big Picture Considerations:


• Will this query perform well when the database grows significantly?
• Should pagination or a different query approach be considered for large datasets?
• Does this method comply with the repository pattern used in the project?
Summary: Big picture thinking during code reviews helps ensure that the code not only meets
immediate requirements but also aligns with broader system goals like scalability, maintainability,
and architectural consistency.

What is an example of something that should block a PR from moving


forward during a review?
One example of something that should block a pull request (PR) from moving forward is **intro-
ducing untested critical functionality**. Critical functionality includes logic that directly impacts
the application’s core behavior, such as authentication, payment processing, or data integrity. If
such functionality lacks proper unit, integration, or end-to-end tests, the risk of introducing bugs
or regressions is significantly high.

Reasons to Block:
• Risk to Stability: Code without tests may break existing functionality or fail in edge cases.
• Increased Technical Debt: Merging untested code creates more work later when bugs
need to be fixed or tests added retroactively.
• Team Standards Violation: Allowing untested critical code undermines team guidelines
for quality and accountability.

372
9.1. Advanced C# Language Features

Listing 9.9: Code example

1 // Example : Adding a new payment processing method without tests


2 public class PaymentProcessor
3 {
4 public void ProcessPayment ( string paymentMethod , decimal amount )
5 {
6 if ( paymentMethod == " CreditCard " )
7 {
8 // Critical payment processing logic
9 Console . WriteLine ( $ " Processing payment of { amount : C } with {
paymentMethod } " ) ;
10 }
11 else
12 {
13 throw new NotSupportedException ( " Payment method not supported " ) ;
14 }
15 }
16 }
17

18 // Missing tests for :


19 // - Validating supported payment methods
20 // - Handling invalid payment methods
21 // - Ensuring calculations for amount are correct

What Should Be Done Instead:


• Add Tests: Write unit tests for all scenarios, including edge cases.
• Use Mocking for Dependencies: Mock external systems like payment gateways to verify
interaction without actual integration.
• Review Test Coverage: Ensure coverage reports show adequate testing of critical paths.

Blocker Criteria:
• Untested Critical Code: Code directly affecting business-critical functionality.
• Introduced Security Vulnerabilities: Hardcoded credentials, unvalidated inputs, etc.
• Breaking Team Standards: Code that violates coding or design guidelines.
Blocking such PRs ensures quality, stability, and adherence to best practices, protecting the
codebase from long-term issues.

373
9.1. Advanced C# Language Features

What is the role of temporary code, and what should we consider when
using it?
Temporary code refers to changes or additions to the codebase that are intended to solve a short-
term problem or serve as a placeholder until a permanent solution is implemented. While tempo-
rary code can be valuable for rapid iteration or mitigating critical issues, it carries risks that need
careful consideration.

Key Considerations:
• Document the Purpose: Clearly state in comments or documentation why the temporary
code exists and the conditions for its removal.
• Set a Removal Plan: Use tools like issue trackers, ‘TODO‘ comments, or feature flags to
ensure temporary code is revisited and replaced.
• Minimize Complexity: Avoid introducing unnecessary complexity or dependencies into
the temporary solution.
• Assess Risks: Evaluate potential technical debt and the impact on the system if the code
becomes permanent by oversight.
• Ensure Quality: Even if temporary, the code should adhere to basic quality standards like
readability, testability, and compliance with project guidelines.
• Plan for Testing: Ensure temporary code is tested to avoid introducing bugs or regressions.
• Avoid Long-Term Use: Prolonged use of temporary code increases technical debt and can
lead to maintainability challenges.

Listing 9.10: Code example

1 // Temporary hard - coded feature toggle


2 public class FeatureToggleService
3 {
4 // TODO : Replace with a configuration - based toggle by 2024 -12 -01
5 public bool IsNewFeatureEnabled = > false ; // Temporary hardcoded value
6 }
7

8 // Temporary usage of the feature toggle


9 public class FeatureController
10 {
11 private readonly FeatureToggleService _featureToggle ;
12

13 public FeatureController ( FeatureToggleService featureToggle )


14 {
15 _featureToggle = featureToggle ;
16 }
17

18 public IActionResult Get ()

374
9.1. Advanced C# Language Features

19 {
20 if ( _featureToggle . IsNewFeatureEnabled )
21 {
22 return Ok ( " New Feature is Enabled " ) ;
23 }
24

25 return Ok ( " Default Feature " ) ;


26 }
27 }

What to Think About:


• Purpose: Is the temporary code justified for solving the immediate problem?
• Timeline: When will this code be replaced, and who is responsible?
• Impact: Does the code introduce unnecessary dependencies or risks if left in place longer
than intended?
By balancing immediate needs with long-term goals, temporary code can be managed effectively
while minimizing potential downsides.

Why is it important to look for dependencies during a code review or


development process?
Looking for dependencies is critical during code reviews and the development process to ensure that
the codebase remains maintainable, secure, and performant. Dependencies can refer to external
libraries, frameworks, APIs, or even internal module relationships. Here’s why it’s important:
• Security Concerns: External dependencies may introduce vulnerabilities, especially if they
are outdated or untrusted. Ensuring dependencies are from reputable sources and are regu-
larly updated reduces security risks.
• Performance Implications: Some dependencies may introduce unnecessary overhead or
bloat, impacting the application’s performance. It’s important to evaluate whether a depen-
dency is efficient and lightweight for the use case.
• Maintainability: Overusing dependencies can make the codebase harder to maintain and
understand. Minimizing the number of dependencies reduces the risk of conflicts or breaking
changes when updating.
• Version Compatibility: Dependencies may have compatibility issues with existing frame-
works, libraries, or runtime environments. It’s essential to verify version compatibility during
development and reviews.
• License Compliance: Dependencies come with various licenses that may have legal or
commercial implications. Reviewing dependencies ensures compliance with project or orga-
nizational policies.

375
9.1. Advanced C# Language Features

• Redundancy Check: A new dependency might duplicate functionality already available in


the project. Ensuring dependencies are necessary avoids redundancy and reduces complexity.
• Code Quality and Reliability: External dependencies may not always follow best prac-
tices or maintain high code quality. Reviewing dependencies ensures that only reliable and
well-maintained libraries are used.
• Long-Term Support and Updates: Some dependencies may become abandoned or lose
support, leading to technical debt. Choosing actively maintained dependencies ensures
future-proofing.

Listing 9.11: Code example

1 {
2 " dependencies " : {
3 " Newtonsoft . Json " : " 13.0.1 " , // Used for JSON serialization
4 " Serilog " : " 2.10.0 " , // Logging library
5 " EntityFrameworkCore " : " 7.0.0 " // ORM for database operations
6 }
7 }

Checklist for Dependency Review:


• Verify that ‘Newtonsoft.Json‘ is required and not duplicating features of ‘System.Text.Json‘.
• Check that ‘Serilog‘ is actively maintained and compatible with the project’s runtime version.
• Confirm that ‘EntityFrameworkCore‘ aligns with the database and project version.
By proactively reviewing dependencies, you ensure that the code remains secure, efficient, and
aligned with project standards.

What are some best practices for performing a pull request (PR) review?
Pull request (PR) reviews are critical for maintaining code quality, ensuring alignment with coding
standards, and fostering collaboration among team members. Here are some best practices to follow
during a PR review:
• Understand the Context: Review the PR description to understand the purpose of the
changes. Look for linked issues, user stories, or acceptance criteria to verify the intended
functionality.
• Start with the Big Picture: Assess whether the code solves the intended problem. Ensure
that the implementation aligns with the project’s architecture and design principles.
• Check for Code Quality: Verify adherence to coding standards and style guidelines. Look
for potential bugs, performance issues, and maintainability concerns.
• Focus on Logic and Functionality: Ensure that the code logic is correct and handles
edge cases. Test the changes locally if possible to verify behavior.

376
9.1. Advanced C# Language Features

• Review Test Coverage: Check if the PR includes adequate unit tests, integration tests,
or end-to-end tests. Verify that existing tests are updated and the overall test coverage is
sufficient.
• Consider Scalability and Performance: Review the code for efficiency, especially in
loops, database queries, or API calls. Suggest optimizations where appropriate.
• Provide Constructive Feedback: Be specific and actionable in your comments (e.g.,
"Consider renaming this variable for clarity"). Avoid criticizing the developer; focus on
improving the code.
• Keep an Eye on Security: Look for potential security vulnerabilities, such as improper
input validation, unencrypted data handling, or exposed secrets.
• Check for Documentation: Ensure that any new or updated functionality is documented
(e.g., README, API docs, or inline comments).
• Review Commit History: Verify that commit messages are clear and descriptive. Ensure
that commits are logically grouped and follow conventions (e.g., "fix:", "feat:", "chore:").
• Balance Speed and Thoroughness: Provide timely feedback to avoid blocking progress.
Ensure that the review is thorough enough to maintain quality without nitpicking minor
issues.
• Encourage Collaboration: Engage in discussions where the intent is unclear. Suggest
alternatives and be open to constructive debate.

Listing 9.12: Code example

1 // Example of test coverage for a PR


2 [ TestClass ]
3 public class CalculatorTests
4 {
5 [ TestMethod ]
6 public void Add_ReturnsCorrectSum ()
7 {
8 // Arrange
9 var calculator = new Calculator () ;
10

11 // Act
12 var result = calculator . Add (2 , 3) ;
13

14 // Assert
15 Assert . AreEqual (5 , result ) ;
16 }
17 }

By adhering to these best practices, PR reviews can enhance code quality, promote learning,
and ensure the maintainability and reliability of the project.

377
9.1. Advanced C# Language Features

What are Lazy<T> types in C#, and when would you use them?
‘Lazy<T>‘ is a type in C# that provides support for **lazy initialization**. It ensures that
the object or value is created only when it is accessed for the first time, avoiding unnecessary
computations or resource usage.

The main advantages of ‘Lazy<T>‘ include:


• Deferred Initialization: Improves performance by delaying object creation until needed.
• Thread Safety: Ensures thread-safe initialization when required.
• Custom Initialization Logic: Allows complex initialization logic using a delegate or fac-
tory method.
You would use ‘Lazy<T>‘ in scenarios where object creation is expensive, and the object may
not always be needed, such as caching, configuration loading, or large data structures.

Listing 9.13: Code example

1 // Example : Using Lazy <T > to defer expensive initialization


2 using System ;
3

4 public class LazyExample


5 {
6 public static void Main ()
7 {
8 // Lazy initialization of an ExpensiveObject
9 Lazy < ExpensiveObject > lazyObject = new Lazy < ExpensiveObject >(() = >
10 {
11 Console . WriteLine ( " ExpensiveObject is being created ... " ) ;
12 return new ExpensiveObject ( " Resource - Intensive Object " ) ;
13 }) ;
14

15 Console . WriteLine ( " Lazy object created but not yet initialized . " ) ;
16

17 // Accessing the value for the first time


18 Console . WriteLine ( $ " Object Name : { lazyObject . Value . Name } " ) ;
19 }
20 }
21

22 public class ExpensiveObject


23 {
24 public string Name { get ; }
25

26 public ExpensiveObject ( string name )


27 {
28 Name = name ;
29 Console . WriteLine ( " ExpensiveObject constructor executed . " ) ;

378
9.1. Advanced C# Language Features

30 }
31 }

What is the advantage of using List<T> in C#, and when would you
use it?
‘List<T>‘ is a generic collection in C# that provides a dynamic array-like structure. Unlike
traditional arrays, it allows for flexible resizing and offers a rich set of methods for managing and
manipulating data. The main advantages of using ‘List<T>‘ include:
• Dynamic Sizing: Automatically resizes as elements are added or removed.
• Type Safety: Ensures all elements in the list are of a specific type ‘T‘.
• Rich API: Provides methods like ‘Add‘, ‘Remove‘, ‘Find‘, and ‘Sort‘ for easy data manip-
ulation.
• Performance: Optimized for fast access with O(1) time complexity for index-based lookups.
• LINQ Integration: Fully compatible with LINQ queries for advanced data operations.
‘List<T>‘ is ideal for scenarios where the number of elements can vary, and operations like
insertion, removal, or sorting are frequently needed.

Listing 9.14: Code example

1 // Example : Using List <T > to manage a collection of items


2 using System ;
3 using System . Collections . Generic ;
4

5 public class ListExample


6 {
7 public static void Main ()
8 {
9 // Create a list of integers
10 List < int > numbers = new List < int > { 1 , 2 , 3 , 4 };
11

12 // Add an element to the list


13 numbers . Add (5) ;
14

15 // Remove an element
16 numbers . Remove (2) ;
17

18 // Sort the list


19 numbers . Sort () ;
20

21 // Display the list


22 Console . WriteLine ( " List contents : " ) ;
23 foreach ( int number in numbers )
24 {

379
9.1. Advanced C# Language Features

25 Console . WriteLine ( number ) ;


26 }
27 }
28 }

What is Object Pooling in C#, and why is it useful?


Object pooling is a design pattern in C# used to optimize the performance of applications by
reusing objects that are expensive to create or initialize. Instead of creating and destroying objects
repeatedly, a pool of pre-initialized reusable objects is maintained. When an object is needed, it
is leased from the pool, and after use, it is returned to the pool.
Object pooling is useful for reducing memory allocation overhead, improving performance in
high-frequency object usage scenarios, and minimizing garbage collection pressure.

Listing 9.15: Code example

1 // Example : Using an object pool with ArrayPool <T >


2 using System ;
3 using System . Buffers ;
4

5 public class ObjectPoolingExample


6 {
7 public static void Main ()
8 {
9 // Create a shared object pool for byte arrays
10 ArrayPool < byte > pool = ArrayPool < byte >. Shared ;
11

12 // Rent an array from the pool


13 byte [] buffer = pool . Rent (1024) ; // Allocate a buffer of at least 1024
bytes
14

15 // Use the buffer


16 Console . WriteLine ( " Using rented buffer ... " ) ;
17 buffer [0] = 42; // Example operation
18

19 // Return the array to the pool


20 pool . Return ( buffer ) ;
21

22 Console . WriteLine ( " Buffer returned to the pool . " ) ;


23 }
24 }

380
9.1. Advanced C# Language Features

What are expression-bodied members in C#, and when would you use
them?
Expression-bodied members are a shorthand syntax in C# that allows you to define simple meth-
ods, properties, and constructors using lambda-like syntax. They are useful when the method or
property consists of a single expression.

Listing 9.16: Code example

1 // Before refactoring : Traditional property syntax


2 public class Person
3 {
4 private string _name ;
5 public string Name
6 {
7 get { return _name ; }
8 set { _name = value ; }
9 }
10 }
11

12 // After refactoring : Expression - bodied property


13 public class Person
14 {
15 private string _name ;
16 public string Name = > _name ;
17 }

What is pattern matching in C#, and how does it enhance code read-
ability?
Pattern matching in C# allows you to match values against patterns and deconstruct them in a
more readable and concise manner. It is especially useful for simplifying type checks and conditional
logic.

Listing 9.17: Code example

1 public void DisplayShapeInfo ( object shape )


2 {
3 if ( shape is Circle c )
4 {
5 Console . WriteLine ( $ " Circle with radius { c . Radius } " ) ;
6 }
7 else if ( shape is Rectangle r )
8 {
9 Console . WriteLine ( $ " Rectangle with width { r . Width } and height { r . Height } " )
;

381
9.1. Advanced C# Language Features

10 }
11 }

How does the ‘ref‘ return feature work in C#, and when would you use
it?
The ‘ref‘ return feature allows a method to return a reference to a variable rather than a copy of
the value. This is useful when you want to modify the returned value directly without creating
additional copies.

Listing 9.18: Code example

1 public class RefReturnExample


2 {
3 private int [] numbers = { 1 , 2 , 3 , 4 , 5 };
4

5 public ref int GetNumberAt ( int index )


6 {
7 return ref numbers [ index ]; // Returning a reference
8 }
9

10 public void UpdateNumber ()


11 {
12 ref int number = ref GetNumberAt (2) ;
13 number = 10; // Modifies the array directly
14 }
15 }

What are tuples in C#, and how do you use them for returning multiple
values from a method?
Tuples in C# provide a way to store multiple values in a single object without creating a custom
class or struct. They are useful for returning multiple values from a method without the need for
complex data structures.

Listing 9.19: Code example

1 public ( int , string ) GetPersonInfo ()


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

6 public void UsePersonInfo ()


7 {
8 var ( age , name ) = GetPersonInfo () ; // Deconstructing the tuple

382
9.1. Advanced C# Language Features

9 Console . WriteLine ( $ " Age : { age } , Name : { name } " ) ;


10 }

What is the purpose of local functions in C#, and how do they differ
from lambda expressions?
Local functions are methods declared inside another method. They are similar to lambda expres-
sions but are more efficient, as they support better scoping rules and can have static types. Local
functions also allow for recursion and are not limited by the closure model used in lambdas.

Listing 9.20: Code example

1 public int Factorial ( int n )


2 {
3 // Local function for recursion
4 int CalculateFactorial ( int number )
5 {
6 if ( number <= 1) return 1;
7 return number * CalculateFactorial ( number - 1) ;
8 }
9

10 return CalculateFactorial ( n ) ;
11 }

How does the ‘async‘ and ‘await‘ keyword pair simplify asynchronous
programming in C#?
The ‘async‘ and ‘await‘ keywords simplify asynchronous programming by allowing you to write
asynchronous code that looks like synchronous code. They help avoid callback hell and improve
readability when dealing with I/O-bound or CPU-bound tasks.

Listing 9.21: Code example

1 public async Task < string > FetchDataAsync ()


2 {
3 HttpClient client = new HttpClient () ;
4 string result = await client . GetStringAsync ( " https :// example . com " ) ;
5 return result ;
6 }

383
9.1. Advanced C# Language Features

How do default interface methods work in C#, and when would you use
them?
Default interface methods allow you to define method implementations in interfaces. This provides
a way to add new methods to existing interfaces without breaking existing implementations. It is
useful for evolving APIs while maintaining backward compatibility.

Listing 9.22: Code example

1 public interface ILogger


2 {
3 void Log ( string message ) ;
4

5 // Default implementation
6 void LogError ( string error ) = > Log ( $ " Error : { error } " ) ;
7 }
8

9 public class ConsoleLogger : ILogger


10 {
11 public void Log ( string message )
12 {
13 Console . WriteLine ( message ) ;
14 }
15 }

What are records in C#, and how do they simplify immutable data mod-
els?
Records in C# are a reference type that provides built-in immutability and value-based equality.
They are ideal for creating data transfer objects (DTOs) or models that primarily hold data,
simplifying the creation of immutable types.

Listing 9.23: Code example

1 // Defining a record
2 public record Person ( string FirstName , string LastName ) ;
3

4 public void UseRecord ()


5 {
6 var person = new Person ( " John " , " Doe " ) ;
7 Console . WriteLine ( person . FirstName ) ; // Outputs " John "
8 }

384
9.1. Advanced C# Language Features

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


generics?
Covariance allows you to use a more derived type than originally specified, while contravariance
allows using a more generic type. They apply to generic interfaces and delegates, enabling more
flexible method and interface design when dealing with inheritance.

Listing 9.24: Code example

1 // Covariance : You can assign an IEnumerable < Derived > to IEnumerable < Base >
2 public class Base { }
3 public class Derived : Base { }
4

5 public void UseCovariance ( IEnumerable < Base > bases )


6 {
7 IEnumerable < Derived > deriveds = new List < Derived >() ;
8 UseCovariance ( deriveds ) ; // Covariant assignment
9 }

What is the purpose of nullable reference types, and how do they help
avoid null reference exceptions?
Nullable reference types allow you to explicitly declare whether a reference type can be null. This
feature helps you avoid null reference exceptions by enforcing nullability checks at compile-time,
making your code more robust.

Listing 9.25: Code example

1 // Enabling nullable reference types ( C # 8.0+)


2 public class Person
3 {
4 public string ? FirstName { get ; set ; } // Nullable reference type
5 public string LastName { get ; set ; } = " Doe " ; // Non - nullable reference type
6 }
7

8 public void UseNullable ()


9 {
10 Person person = new Person () ;
11 Console . WriteLine ( person . FirstName ?. ToUpper () ) ; // Safe to call with null -
conditional operator
12 }

385
9.1. Advanced C# Language Features

How does the ‘using‘ declaration simplify resource management in C#


8.0 and later?
The ‘using‘ declaration in C# 8.0 and later simplifies resource management by ensuring that a
disposable resource is automatically disposed of at the end of the scope without requiring an
explicit ‘using‘ block.

Listing 9.26: Code example

1 // Before C # 8.0: Traditional using block


2 public void ProcessFile ()
3 {
4 using ( var reader = new StreamReader ( " file . txt " ) )
5 {
6 string content = reader . ReadToEnd () ;
7 } // reader is disposed here
8 }
9

10 // After C # 8.0: Using declaration


11 public void ProcessFile ()
12 {
13 var reader = new StreamReader ( " file . txt " ) ; // Implicitly disposed at the end
of the method
14 string content = reader . ReadToEnd () ;
15 }

What is the difference between ‘lock‘ and ‘Monitor‘ in C#, and when
would you use each?
‘lock‘ is a syntactic shortcut for acquiring and releasing a monitor on an object, whereas ‘Monitor‘
provides more advanced threading synchronization features like ‘TryEnter‘, ‘Wait‘, and ‘Pulse‘.
‘Monitor‘ should be used when you need fine-grained control over thread synchronization.

Listing 9.27: Code example

1 // Using lock ( shortcut for Monitor . Enter / Exit )


2 private readonly object _lock = new object () ;
3 public void DoWork ()
4 {
5 lock ( _lock )
6 {
7 // Critical section
8 }
9 }
10

11 // Using Monitor for more control

386
9.1. Advanced C# Language Features

12 public void DoWorkWithMonitor ()


13 {
14 bool lockTaken = false ;
15 try
16 {
17 Monitor . Enter ( _lock , ref lockTaken ) ;
18 // Critical section
19 }
20 finally
21 {
22 if ( lockTaken ) Monitor . Exit ( _lock ) ;
23 }
24 }

How does the ‘Span<T>‘ type in C# improve performance when dealing


with large arrays and memory?
‘Span<T>‘ is a stack-allocated type that provides a way to work with slices of arrays or memory
buffers efficiently. It helps reduce memory allocations and improves performance by enabling
zero-copy slicing of data.

Listing 9.28: Code example

1 public void UseSpan ()


2 {
3 int [] numbers = { 1 , 2 , 3 , 4 , 5 };
4

5 Span < int > span = numbers . AsSpan (1 , 3) ; // Slice of the array (2 , 3 , 4)
6 span [0] = 10; // Modifies the original array
7 Console . WriteLine ( string . Join ( " , " , numbers ) ) ; // Outputs : 1 , 10 , 3 , 4 , 5
8 }

How does the ‘is‘ keyword with patterns enhance type checking in C#?
The ‘is‘ keyword with patterns simplifies type checking and casting by allowing you to combine
type checks and pattern matching in a single expression. This makes the code more readable and
concise.

Listing 9.29: Code example

1 public void ProcessShape ( object shape )


2 {
3 if ( shape is Circle { Radius : > 0 } c )
4 {
5 Console . WriteLine ( $ " Circle with radius { c . Radius } " ) ;
6 }

387
9.1. Advanced C# Language Features

7 else if ( shape is Rectangle { Width : > 0 , Height : > 0 } r )


8 {
9 Console . WriteLine ( $ " Rectangle with width { r . Width } and height { r . Height } " )
;
10 }
11 }

What is ‘dynamic‘ typing in C#, and when would you use it?
‘dynamic‘ typing in C# allows you to bypass compile-time type checking and defer type resolu-
tion until runtime. It is useful when working with COM objects, interop, or dynamically-typed
languages, but it comes with a performance penalty and risks runtime errors.

Listing 9.30: Code example

1 public void UseDynamic ()


2 {
3 dynamic obj = " Hello , World ! " ;
4 Console . WriteLine ( obj . Length ) ; // Resolved at runtime
5

6 obj = 123;
7 Console . WriteLine ( obj + 10) ; // Resolved at runtime
8 }

How do indexers work in C#, and how can you use them to create more
intuitive classes?
Indexers allow objects to be indexed in a similar way to arrays. They provide a syntactic sugar for
accessing elements in a collection-like object, making the class more intuitive and user-friendly.

Listing 9.31: Code example

1 public class BookCollection


2 {
3 private readonly Dictionary < int , string > books = new Dictionary < int , string >()
;
4

5 public string this [ int index ]


6 {
7 get = > books . ContainsKey ( index ) ? books [ index ] : " Not Found " ;
8 set = > books [ index ] = value ;
9 }
10 }
11

12 public void UseIndexer ()


13 {

388
9.1. Advanced C# Language Features

14 var collection = new BookCollection () ;


15 collection [1] = " C # in Depth " ;
16 Console . WriteLine ( collection [1]) ; // Outputs : C # in Depth
17 }

How does the ‘init‘ keyword in C# 9.0 improve immutability in class


properties?
The ‘init‘ keyword in C# 9.0 allows properties to be set during object initialization but makes
them immutable after that. This allows for immutable objects with a more concise and readable
initialization syntax.

Listing 9.32: Code example

1 public class Person


2 {
3 public string FirstName { get ; init ; }
4 public string LastName { get ; init ; }
5 }
6

7 public void CreatePerson ()


8 {
9 var person = new Person { FirstName = " John " , LastName = " Doe " };
10 // person . FirstName = " Jane "; // Error : Property is immutable after
initialization
11 }

What is the ‘file-scoped namespace‘ feature in C# 10.0, and how does it


simplify namespace declarations?
File-scoped namespaces in C# 10.0 allow you to declare a namespace that applies to an entire
file without needing to wrap the entire code block in curly braces. This simplifies namespace
declarations, especially in large files.

Listing 9.33: Code example

1 // Before C # 10.0
2 namespace MyApp
3 {
4 public class Program
5 {
6 // Code here
7 }
8 }
9

389
9.1. Advanced C# Language Features

10 // After C # 10.0: File - scoped namespace


11 namespace MyApp ;
12

13 public class Program


14 {
15 // Code here
16 }

How does the ‘switch expression‘ in C# improve code readability com-


pared to traditional ‘switch‘ statements?
The ‘switch expression‘ in C# provides a more concise and functional way to express conditional
logic compared to the traditional ‘switch‘ statement. It allows for inline pattern matching and
results in cleaner and more readable code.

Listing 9.34: Code example

1 // Traditional switch statement


2 public string GetDayOfWeek ( int day )
3 {
4 switch ( day )
5 {
6 case 1: return " Monday " ;
7 case 2: return " Tuesday " ;
8 default : return " Unknown " ;
9 }
10 }
11

12 // Switch expression ( C # 8.0+)


13 public string GetDayOfWeek ( int day ) = > day switch
14 {
15 1 = > " Monday " ,
16 2 = > " Tuesday " ,
17 _ = > " Unknown "
18 };

How does the ‘async‘ stream feature (‘IAsyncEnumerable<T>‘) enhance


performance when working with large data sets?
‘IAsyncEnumerable<T>‘ allows you to asynchronously stream data in chunks instead of waiting
for the entire data set to be available. This improves performance and memory usage when working
with large data sets or long-running asynchronous operations.

Listing 9.35: Code example

390
9.2. Compilation, Managed vs. Unmanaged Code, Intermediate Language (IL), CLR

1 public async IAsyncEnumerable < int > GetNumbersAsync ()


2 {
3 for ( int i = 1; i <= 5; i ++)
4 {
5 await Task . Delay (1000) ; // Simulate 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 ) ; // Numbers are streamed asynchronously
15 }
16 }

9.2 Compilation, Managed vs. Unmanaged Code, Interme-


diate Language (IL), CLR
What is the difference between managed and unmanaged code in .NET?
Managed code is executed by the Common Language Runtime (CLR) in .NET and benefits from
features like garbage collection, type safety, and exception handling. Unmanaged code is executed
directly by the operating system and does not run under the control of the CLR. It typically refers
to code written in languages like C or C++.
Listing 9.36: Code example

1 // Managed code example


2 public class ManagedClass
3 {
4 public void DoWork ()
5 {
6 Console . WriteLine ( " This is managed code executed by the CLR . " ) ;
7 }
8 }
9

10 // Unmanaged code example ( interop with native C ++)


11 [ DllImport ( " kernel32 . dll " ) ]
12 public static extern void Sleep ( uint dwMilliseconds ) ;
13

14 public void CallUnmanagedCode ()


15 {
16 Sleep (1000) ; // Calls unmanaged code in Windows API

391
9.2. Compilation, Managed vs. Unmanaged Code, Intermediate Language (IL), CLR

17 }

How does the .NET compilation process work?


In .NET, the compilation process involves two steps:
• ‘C# code to Intermediate Language (IL)‘: The C# source code is compiled into IL (also
known as MSIL or CIL), which is platform-independent. This process is done by the C#
compiler which is external to the CLR.
• ‘IL to machine code‘: The IL is then compiled to native machine code by the Just-In-Time
(JIT) compiler at runtime (which is part of the CLR), which allows the code to be executed
on the target system.
• ‘Running machine transformed code‘: The CLR manages execution, provides runtime services
(memory, threading, exception handling).

Listing 9.37: Code example

1 // Compilation steps overview ( not actual code ) :


2 // 1. C # code -> IL ( compiled by the C # compiler )
3 // 2. IL -> Machine code ( compiled by the JIT compiler )
4

5 // Example of a simple method


6 public int AddNumbers ( int a , int b )
7 {
8 return a + b ;
9 }

What is Intermediate Language (IL) in .NET, and why is it important?


Intermediate Language (IL) is a low-level, platform-independent programming language that .NET
languages like C# are compiled into. It allows .NET code to be executed on any platform that
has a CLR implementation, enabling cross-platform compatibility.

Listing 9.38: Code example

1 // This C # code :
2 public int Multiply ( int a , int b )
3 {
4 return a * b ;
5 }
6

7 // Is compiled into IL code similar to :


8 . method public hidebysig
9 instance int32 Multiply ( int32 a , int32 b ) cil managed
10 {
11 // Code size 6 (0 x6 )

392
9.2. Compilation, Managed vs. Unmanaged Code, Intermediate Language (IL), CLR

12 . maxstack 2
13 IL_0000 : ldarg .1
14 IL_0001 : ldarg .2
15 IL_0002 : mul
16 IL_0003 : ret
17 }

How does the Just-In-Time (JIT) compiler work in .NET?


The JIT compiler in .NET compiles Intermediate Language (IL) to native machine code at runtime.
The JIT compiler compiles methods on an as-needed basis, which means only the methods that
are called during execution are compiled, optimizing the startup time and resource usage.

Listing 9.39: Code example

1 // When the following method is called , the JIT will compile it to native machine
code
2 public void PerformCalculation ()
3 {
4 int result = Add (5 , 10) ; // JIT compiles this method when it ’s called
5 }
6

7 public int Add ( int a , int b ) = > a + b ;

What is the Common Language Runtime (CLR), and what is its role in
.NET?
The Common Language Runtime (CLR) is the execution engine for .NET applications. It manages
memory, handles garbage collection, enforces type safety, provides security, and compiles IL to
native code via the JIT compiler. It also provides a standard execution environment across all
supported languages.

Listing 9.40: Code example

1 // CLR ensures that managed code executes in a safe and controlled environment
2 public class HelloWorld
3 {
4 public void SayHello ()
5 {
6 Console . WriteLine ( " Hello , World ! " ) ;
7 }
8 }
9

10 // The CLR handles memory management , garbage collection , and type safety for this
code

393
9.2. Compilation, Managed vs. Unmanaged Code, Intermediate Language (IL), CLR

How does garbage collection work in the CLR, and why is it important?
Garbage collection (GC) in the CLR automatically reclaims memory occupied by objects that are
no longer in use. It eliminates the need for manual memory management, reducing the chances of
memory leaks and improving the stability of applications. The GC in .NET operates in generations
to optimize memory allocation and collection.
Listing 9.41: Code example

1 public class Person


2 {
3 public string Name { get ; set ; }
4

5 public void DisposeOfPerson ()


6 {
7 // No need to manually free memory ; the CLR ’s garbage collector handles it
8 // when this object is no longer referenced
9 }
10 }
11

12 // The garbage collector will reclaim memory when this Person instance is no
longer used
13 Person p = new Person { Name = " John " };
14 p = null ; // Now available for garbage collection

What is the difference between IL code and native machine code?


IL (Intermediate Language) code is a platform-independent, high-level representation of compiled
.NET code that is further compiled into native machine code at runtime by the JIT compiler.
Native machine code is the low-level code that is executed directly by the CPU and is specific to
the architecture of the machine.
Listing 9.42: Code example

1 // C # code :
2 public int Subtract ( int x , int y ) = > x - y ;
3

4 // IL code ( simplified ) :
5 . method public hidebysig instance int32 Subtract ( int32 x , int32 y ) cil managed
6 {
7 IL_0000 : ldarg .1
8 IL_0001 : ldarg .2
9 IL_0002 : sub
10 IL_0003 : ret
11 }
12

13 // Native machine code generated by JIT at runtime ( architecture - specific )

394
9.2. Compilation, Managed vs. Unmanaged Code, Intermediate Language (IL), CLR

How does the CLR handle exceptions, and how is this different from
unmanaged code?
In managed code, exceptions are handled by the CLR using a structured exception-handling model.
The CLR provides a safe environment where exceptions can be caught and handled. In unmanaged
code, exceptions are often handled manually using error codes or platform-specific mechanisms like
‘try-catch‘ blocks in C++. CLR exceptions offer stack tracing and built-in exception types.

Listing 9.43: Code example

1 public void DivideNumbers ( int a , int b )


2 {
3 try
4 {
5 int result = a / b ;
6 }
7 catch ( DivideByZeroException ex )
8 {
9 Console . WriteLine ( $ " Error : { ex . Message } " ) ;
10 }
11 }

What is ngen.exe, and how does it improve application startup time in


.NET?
‘ngen.exe‘ (Native Image Generator) is a tool that precompiles IL code to native machine code
ahead of time (AOT). It generates native images of assemblies that can be used by the CLR
to avoid JIT compilation at runtime, improving startup time for applications that benefit from
ahead-of-time compilation.

Listing 9.44: Code example

1 // ngen . exe generates a native image for an assembly like this :


2 // ngen install MyAssembly . dll
3

4 // Once installed , the CLR uses the precompiled native image , skipping the JIT
step

What is the Global Assembly Cache (GAC), and how is it used in .NET?
The Global Assembly Cache (GAC) is a machine-wide repository used to store shared assemblies
in .NET. Assemblies in the GAC are globally accessible by all applications on the machine, which
helps avoid duplication and conflicts when different applications use the same library. Assemblies
placed in the GAC must have a strong name.

395
9.2. Compilation, Managed vs. Unmanaged Code, Intermediate Language (IL), CLR

Listing 9.45: Code example

1 // Strongly named assembly placed in GAC


2 // Example : gacutil / i MyLibrary . dll
3

4 // Referencing a GAC assembly in a project :


5 using MyLibrary ;
6 public class Program
7 {
8 public void UseGacLibrary ()
9 {
10 var lib = new MyLibraryClass () ;
11 lib . DoSomething () ;
12 }
13 }

What are strong-named assemblies, and how do they ensure assembly


versioning in .NET?
A strong-named assembly has a unique identity that includes its name, version number, culture
information, and a public key token. Strong names are used to ensure that assemblies with the
same name but different versions do not conflict. Strong-named assemblies can be placed in the
Global Assembly Cache (GAC).
Listing 9.46: Code example

1 // Example of strong - naming an assembly :


2 // sn . exe -k MyKey . snk
3 // csc / keyfile : MyKey . snk / out : MyStrongNamedAssembly . dll MyAssembly . cs
4

5 // The assembly now has a strong name , allowing it to be placed in the GAC

What are P/Invoke and COM interop, and how do they enable interac-
tion with unmanaged code in .NET?
P/Invoke (Platform Invocation Services) allows managed code to call functions in unmanaged
libraries (e.g., Windows DLLs), while COM interop allows managed code to interact with COM
components. Both mechanisms enable .NET applications to work with existing unmanaged code
written in languages like C or C++.
Listing 9.47: Code example

1 // P / Invoke example :
2 [ DllImport ( " user32 . dll " ) ]
3 public static extern int MessageBox ( IntPtr hWnd , string text , string caption , uint
type ) ;

396
9.2. Compilation, Managed vs. Unmanaged Code, Intermediate Language (IL), CLR

5 // COM Interop example :


6 [ ComImport , Guid ( " 00020400 -0000 -0000 - C000 -000000000046 " ) ]
7 public interface IDispatch
8 {
9 // Methods to interact with COM objects
10 }

How does the CLR enforce type safety, and why is it important in man-
aged code?
The CLR enforces type safety by ensuring that objects are only accessed in ways that are com-
patible with their data types. This prevents memory corruption, type mismatch errors, and other
runtime issues. Type safety is critical in managed code because it ensures that the integrity of the
memory is maintained, allowing the CLR to safely manage memory.
Listing 9.48: Code example

1 // Type safety in managed code


2 public class TypeSafeExample
3 {
4 public void PrintMessage ( object input )
5 {
6 if ( input is string message )
7 {
8 Console . WriteLine ( message ) ; // Safe cast to string
9 }
10 }
11 }

What is the difference between assembly loading in managed and un-


managed code?
In managed code, the CLR handles assembly loading, resolving references, and ensuring version
compatibility through mechanisms like the Global Assembly Cache (GAC). In unmanaged code,
loading dynamic libraries is handled manually using platform-specific APIs like ‘LoadLibrary‘ on
Windows.
Listing 9.49: Code example

1 // Managed code assembly loading :


2 Assembly assembly = Assembly . Load ( " MyLibrary " ) ;
3

4 // Unmanaged code ( C ++) :


5 HMODULE hModule = LoadLibrary ( L " MyLibrary . dll " ) ;

397
9.2. Compilation, Managed vs. Unmanaged Code, Intermediate Language (IL), CLR

What are the different types of JIT compilation in .NET, and when would
each be used?
There are three types of JIT compilation in .NET:
• Normal JIT: Compiles methods when they are first called.
• Econo JIT: Optimizes for memory, using less memory but with slower execution.
• Pre-JIT: Precompiles the entire assembly at application startup, typically used in scenarios
like AOT compilation (e.g., ‘ngen‘).

Listing 9.50: Code example

1 // Example of how JIT works on method invocation


2 public void Example ()
3 {
4 Console . WriteLine ( " This method will be JIT compiled on first execution . " ) ;
5 }

What is the role of metadata in .NET assemblies, and how does the CLR
use it?
Metadata in .NET assemblies contains information about the types, members, and references
within the assembly. The CLR uses metadata to load types, enforce security, and manage execu-
tion. Metadata is stored alongside the IL code in .NET assemblies.

Listing 9.51: Code example

1 // Accessing metadata at runtime using reflection


2 public void PrintAssemblyMetadata ()
3 {
4 Assembly assembly = Assembly . GetExecutingAssembly () ;
5 foreach ( var type in assembly . GetTypes () )
6 {
7 Console . WriteLine ( $ " Type : { type . Name } " ) ;
8 }
9 }

What are generational garbage collections in .NET, and how do they


optimize memory management?
Generational garbage collection divides objects into three generations (0, 1, and 2) based on their
lifespan. Generation 0 contains short-lived objects, while Generation 2 contains long-lived objects.
This approach optimizes memory management by focusing garbage collection efforts on short-lived
objects, reducing the performance cost of collecting long-lived objects.

398
9.2. Compilation, Managed vs. Unmanaged Code, Intermediate Language (IL), CLR

Listing 9.52: Code example

1 public void MemoryOptimizationExample ()


2 {
3 // Generation 0 objects
4 string shortLivedObject = " Temporary string " ;
5

6 // Generation 2 object
7 string [] longLivedArray = new string [10000];
8 }

How does the CLR provide security features like Code Access Security
(CAS) in .NET?
Code Access Security (CAS) is a security feature in the CLR that restricts the access of managed
code to protected resources based on the permissions granted to the code. CAS ensures that
code can only perform actions it has been explicitly granted permission for, reducing the risk of
malicious actions.

Listing 9.53: Code example

1 // Example of CAS ( deprecated in . NET Core and later ) :


2 [ PermissionSet ( SecurityAction . Demand , Name = " FullTrust " ) ]
3 public class SecureClass
4 {
5 public void PerformSecureAction ()
6 {
7 // Action requires full trust
8 }
9 }

How does cross-language interoperability work in .NET via the CLR?


The CLR enables cross-language interoperability by providing a common runtime environment for
multiple languages like C#, F#, and VB.NET. The CLR enforces type safety and uses metadata
and IL code to allow different languages to interact seamlessly within the same .NET application.

Listing 9.54: Code example

1 // Example of using C # and F # together in a . NET application


2 // C # code calling an F # function :
3 public class Program
4 {
5 public static void Main ()
6 {
7 FSharpLibrary . FSharpModule . HelloWorld () ;

399
9.3. Reflection and Dynamic Code

8 }
9 }
10

11 // F # code :
12 module FSharpModule =
13 let HelloWorld () =
14 printfn " Hello from F # "

9.3 Reflection and Dynamic Code


What is reflection in C#, and why is it used?
Reflection is the ability in C# to inspect and interact with the metadata and types of an assembly
at runtime. It is commonly used for tasks like inspecting the properties and methods of objects,
dynamically invoking methods, or creating instances of types at runtime.

Listing 9.55: Code example

1 public void UseReflection ()


2 {
3 Type type = typeof ( Person ) ;
4 Console . WriteLine ( $ " Class name : { type . Name } " ) ;
5

6 // Get all properties of the class


7 foreach ( var prop in type . GetProperties () )
8 {
9 Console . WriteLine ( $ " Property : { prop . Name } " ) ;
10 }
11 }

How can you dynamically invoke a method using reflection in C#?


You can use reflection to dynamically invoke a method by first obtaining a ‘MethodInfo‘ object
and then calling the ‘Invoke‘ method on that ‘MethodInfo‘ object. This is useful when the method
to be invoked is not known at compile-time.

Listing 9.56: Code example

1 public void InvokeMethodDynamically ()


2 {
3 Type type = typeof ( Person ) ;
4 object instance = Activator . CreateInstance ( type ) ;
5

6 MethodInfo methodInfo = type . GetMethod ( " SayHello " ) ;


7 methodInfo . Invoke ( instance , null ) ; // Dynamically invoking the method

400
9.3. Reflection and Dynamic Code

8 }

How can reflection be used to dynamically create an instance of a class


in C#?
You can use the ‘Activator.CreateInstance()‘ method to dynamically create an instance of a class
using reflection. This is useful when the class type is not known at compile-time.

Listing 9.57: Code example

1 public void CreateInstanceDynamically ()


2 {
3 Type type = typeof ( Person ) ;
4 object instance = Activator . CreateInstance ( type ) ;
5

6 Console . WriteLine ( $ " Instance created of type : { instance . GetType () . Name } " ) ;
7 }

How do you access private fields or methods using reflection in C#?


You can access private fields or methods using reflection by specifying ‘BindingFlags.NonPublic‘
in combination with ‘BindingFlags.Instance‘ or ‘BindingFlags.Static‘, depending on the context.

Listing 9.58: Code example

1 public void AccessPrivateField ()


2 {
3 Type type = typeof ( Person ) ;
4 object instance = Activator . CreateInstance ( type ) ;
5

6 FieldInfo privateField = type . GetField ( " _age " , BindingFlags . NonPublic |


BindingFlags . Instance ) ;
7 int ageValue = ( int ) privateField . GetValue ( instance ) ;
8 Console . WriteLine ( $ " Private field value : { ageValue } " ) ;
9 }

How do you modify the value of a property using reflection in C#?


To modify a property using reflection, you can use the ‘PropertyInfo.SetValue()‘ method, which
allows you to set the value of a property dynamically at runtime.

Listing 9.59: Code example

1 public void ModifyPropertyValue ()


2 {
3 Type type = typeof ( Person ) ;

401
9.3. Reflection and Dynamic Code

4 object instance = Activator . CreateInstance ( type ) ;


5

6 PropertyInfo nameProperty = type . GetProperty ( " Name " ) ;


7 nameProperty . SetValue ( instance , " John Doe " ) ;
8

9 Console . WriteLine ( $ " Updated Name : { nameProperty . GetValue ( instance ) } " ) ;


10 }

How can you get the attributes of a class or method using reflection in
C#?
You can use the ‘GetCustomAttributes()‘ method to retrieve the attributes applied to a class,
method, or other members. This is commonly used to inspect metadata like ‘[Obsolete]‘ or custom
attributes.

Listing 9.60: Code example

1 public void GetAttributes ()


2 {
3 Type type = typeof ( Person ) ;
4 var attributes = type . GetCustomAttributes ( false ) ;
5

6 foreach ( var attr in attributes )


7 {
8 Console . WriteLine ( $ " Attribute : { attr . GetType () . Name } " ) ;
9 }
10 }

What is the ‘dynamic‘ keyword in C#, and how does it differ from using
reflection?
The ‘dynamic‘ keyword allows you to bypass compile-time type checking, deferring type resolution
until runtime. Unlike reflection, ‘dynamic‘ provides simpler syntax for invoking methods and
properties dynamically, but it is less type-safe and incurs more overhead.

Listing 9.61: Code example

1 public void UseDynamicKeyword ()


2 {
3 dynamic person = new Person () ;
4 person . Name = " Jane Doe " ; // No compile - time type checking
5

6 Console . WriteLine ( person . Name ) ;


7 }

402
9.3. Reflection and Dynamic Code

How can you load an assembly at runtime and invoke a method using
reflection in C#?
You can load an assembly at runtime using ‘Assembly.LoadFrom()‘ or ‘Assembly.Load()‘, and then
use reflection to inspect and invoke methods from types within the assembly.

Listing 9.62: Code example

1 public void LoadAssemblyAndInvokeMethod ()


2 {
3 Assembly assembly = Assembly . LoadFrom ( " ExternalLibrary . dll " ) ;
4 Type type = assembly . GetType ( " ExternalLibrary . SomeClass " ) ;
5 object instance = Activator . CreateInstance ( type ) ;
6

7 MethodInfo methodInfo = type . GetMethod ( " SomeMethod " ) ;


8 methodInfo . Invoke ( instance , null ) ; // Invoke method from the loaded assembly
9 }

How does reflection affect performance in C#, and how can you mitigate
its impact?
Reflection incurs a performance penalty because it bypasses normal compile-time optimizations
and relies on runtime lookups. To mitigate this, you can cache frequently accessed types and
members, or use reflection only when necessary.

Listing 9.63: Code example

1 public void CachedReflection ()


2 {
3 Type type = typeof ( Person ) ;
4 PropertyInfo nameProperty = type . GetProperty ( " Name " ) ; // Cache PropertyInfo
5

6 object instance = Activator . CreateInstance ( type ) ;


7 nameProperty . SetValue ( instance , " John Doe " ) ; // Use cached PropertyInfo
8 }

What is ‘Type.InvokeMember‘ in reflection, and how is it used in C#?


‘Type.InvokeMember‘ is a method that allows you to invoke a member (method, property, field,
etc.) dynamically by name. It can be used for both static and instance members and is a powerful,
albeit complex, tool for working with reflection.

Listing 9.64: Code example

1 public void InvokeMemberExample ()


2 {

403
9.3. Reflection and Dynamic Code

3 Type type = typeof ( Person ) ;


4 object instance = Activator . CreateInstance ( type ) ;
5

6 type . InvokeMember ( " SayHello " , BindingFlags . InvokeMethod | BindingFlags .


Instance | BindingFlags . Public , null , instance , null ) ;
7 }

How can you dynamically load and call methods from an external DLL
using reflection?
You can load an external DLL using ‘Assembly.LoadFrom()‘ and use reflection to invoke methods
from the types defined in that assembly. This allows for dynamic plugin systems or executing code
from external libraries.

Listing 9.65: Code example

1 public void CallExternalDllMethod ()


2 {
3 Assembly assembly = Assembly . LoadFrom ( " ExternalLibrary . dll " ) ;
4 Type externalType = assembly . GetType ( " ExternalLibrary . SomeClass " ) ;
5 object instance = Activator . CreateInstance ( externalType ) ;
6

7 MethodInfo method = externalType . GetMethod ( " SomeMethod " ) ;


8 method . Invoke ( instance , null ) ;
9 }

What is the role of ‘Type.GetType()‘ in reflection, and how do you use


it?
‘Type.GetType()‘ is used to retrieve a ‘Type‘ object based on a string representation of the type
name, including namespace. This is useful when dynamically resolving types at runtime.

Listing 9.66: Code example

1 public void GetTypeFromName ()


2 {
3 string typeName = " System . String " ;
4 Type type = Type . GetType ( typeName ) ;
5

6 if ( type != null )
7 {
8 Console . WriteLine ( $ " Type found : { type . FullName } " ) ;
9 }
10 }

404
9.3. Reflection and Dynamic Code

How can you handle missing members or methods when using reflection
in C#?
When using reflection to access members or methods, you should always check for ‘null‘ to handle
cases where the specified member or method does not exist. This prevents runtime exceptions
from being thrown.

Listing 9.67: Code example

1 public void HandleMissingMember ()


2 {
3 Type type = typeof ( Person ) ;
4 MethodInfo methodInfo = type . GetMethod ( " NonExistentMethod " ) ;
5

6 if ( methodInfo == null )
7 {
8 Console . WriteLine ( " Method not found . " ) ;
9 }
10 else
11 {
12 object instance = Activator . CreateInstance ( type ) ;
13 methodInfo . Invoke ( instance , null ) ;
14 }
15 }

How can you use reflection to inspect the return type of a method in
C#?
Using reflection, you can retrieve a method’s ‘MethodInfo‘ and inspect its return type using the
‘ReturnType‘ property. This is useful when you need to dynamically verify or process the return
value of methods.

Listing 9.68: Code example

1 public void InspectReturnType ()


2 {
3 Type type = typeof ( Person ) ;
4 MethodInfo methodInfo = type . GetMethod ( " GetAge " ) ;
5

6 Console . WriteLine ( $ " Return type of GetAge : { methodInfo . ReturnType } " ) ;


7 }

405
9.3. Reflection and Dynamic Code

How do you use reflection to list all methods and properties of a class in
C#?
To list all methods and properties of a class, you can use the ‘GetMethods()‘ and ‘GetProperties()‘
methods provided by the ‘Type‘ class. This allows you to inspect all available members of a class
at runtime.

Listing 9.69: Code example

1 public void ListAllMembers ()


2 {
3 Type type = typeof ( Person ) ;
4

5 Console . WriteLine ( " Methods : " ) ;


6 foreach ( var method in type . GetMethods () )
7 {
8 Console . WriteLine ( method . Name ) ;
9 }
10

11 Console . WriteLine ( " Properties : " ) ;


12 foreach ( var property in type . GetProperties () )
13 {
14 Console . WriteLine ( property . Name ) ;
15 }
16 }

How can you use reflection to get a list of all constructors of a class in
C#?
You can use the ‘GetConstructors()‘ method provided by the ‘Type‘ class to retrieve a list of all
constructors defined in a class. This is useful for dynamically creating instances of objects when
constructors are not known at compile-time.

Listing 9.70: Code example

1 public void ListAllConstructors ()


2 {
3 Type type = typeof ( Person ) ;
4 ConstructorInfo [] constructors = type . GetConstructors () ;
5

6 foreach ( var constructor in constructors )


7 {
8 Console . WriteLine ( $ " Constructor : { constructor } " ) ;
9 }
10 }

406
9.3. Reflection and Dynamic Code

How do you use reflection to invoke a generic method in C#?


To invoke a generic method using reflection, you first need to obtain the ‘MethodInfo‘ object of
the generic method, then use the ‘MakeGenericMethod()‘ method to create a concrete version of
the generic method with the appropriate type parameters.
Listing 9.71: Code example

1 public void InvokeGenericMethod ()


2 {
3 Type type = typeof ( GenericClass ) ;
4 MethodInfo methodInfo = type . GetMethod ( " GenericMethod " ) ;
5 MethodInfo genericMethod = methodInfo . MakeGenericMethod ( typeof ( int ) ) ; //
Specify the generic type
6

7 object instance = Activator . CreateInstance ( type ) ;


8 genericMethod . Invoke ( instance , new object [] { 42 }) ;
9 }

What are the security implications of using reflection, and how can you
mitigate them?
Reflection can expose private or sensitive members of a class, making it a potential security risk.
To mitigate this, access to reflection should be restricted, and proper permission checks should be
enforced. In .NET Core, accessing private members via reflection is restricted by default.
Listing 9.72: Code example

1 // Example of securing reflection by limiting its usage to known cases


2 public void SecureReflection ()
3 {
4 Type type = typeof ( SensitiveClass ) ;
5 MethodInfo methodInfo = type . GetMethod ( " SensitiveMethod " , BindingFlags .
NonPublic | BindingFlags . Instance ) ;
6

7 if ( methodInfo == null )
8 {
9 Console . WriteLine ( " Method access is restricted . " ) ;
10 }
11 }

How can you use reflection to access custom attributes applied to a


method in C#?
You can use the ‘GetCustomAttributes()‘ method to access any custom attributes applied to a
method. This is commonly used in scenarios where attributes provide metadata or behavior to be

407
9.3. Reflection and Dynamic Code

inspected at runtime.

Listing 9.73: Code example

1 [ MyCustom ( " Example attribute " ) ]


2 public void SomeMethod () { }
3

4 public void AccessCustomAttributes ()


5 {
6 Type type = typeof ( MyClass ) ;
7 MethodInfo method = type . GetMethod ( " SomeMethod " ) ;
8

9 object [] attributes = method . GetCustomAttributes ( false ) ;


10 foreach ( var attr in attributes )
11 {
12 Console . WriteLine ( $ " Attribute : { attr . GetType () . Name } " ) ;
13 }
14 }

How do you use reflection to create and execute lambda expressions dy-
namically in C#?
You can use reflection in combination with expression trees to dynamically create and execute
lambda expressions at runtime. This allows for powerful dynamic code generation and execution.

Listing 9.74: Code example

1 public void DynamicLambdaExpression ()


2 {
3 // Create a parameter expression
4 var param = Expression . Parameter ( typeof ( int ) , " x " ) ;
5

6 // Create a lambda expression x = > x * 2


7 var lambda = Expression . Lambda < Func < int , int > >(
8 Expression . Multiply ( param , Expression . Constant (2) ) ,
9 param
10 );
11

12 // Compile and execute the lambda


13 var func = lambda . Compile () ;
14 int result = func (5) ; // Output : 10
15

16 Console . WriteLine ( result ) ;


17 }

408
9.4. New Features and Enhancements in .NET

9.4 New Features and Enhancements in .NET


What is the ‘init‘ accessor introduced in C# 9.0, and how does it enhance
immutability?
The ‘init‘ accessor in C# 9.0 allows properties to be set only during object initialization, enhancing
immutability by preventing changes after the object is constructed. It provides a more concise way
to create immutable objects.

Listing 9.75: Code example

1 public class Person


2 {
3 public string FirstName { get ; init ; }
4 public string LastName { get ; init ; }
5 }
6

7 public void CreatePerson ()


8 {
9 var person = new Person { FirstName = " John " , LastName = " Doe " };
10 // person . FirstName = " Jane "; // Compile - time error : cannot be assigned
11 }

What are record types in C# 9.0, and how do they differ from regular
classes?
Record types are a new reference type in C# 9.0 that provide built-in immutability and value-based
equality. Unlike regular classes, records are designed to represent data models and are more suited
for scenarios where you want to compare objects by their content rather than their reference.

Listing 9.76: Code example

1 public record Person ( string FirstName , string LastName ) ;


2

3 public void UseRecord ()


4 {
5 var person1 = new Person ( " John " , " Doe " ) ;
6 var person2 = new Person ( " John " , " Doe " ) ;
7

8 Console . WriteLine ( person1 == person2 ) ; // True : Value - based equality


9 }

409
9.4. New Features and Enhancements in .NET

How does the ‘with‘ expression work with record types in C# 9.0?
The ‘with‘ expression in C# 9.0 allows you to create a new instance of a record while copying
values from an existing record and modifying only specific properties. This is useful for maintaining
immutability while changing selected values.

Listing 9.77: Code example

1 public record Person ( string FirstName , string LastName ) ;


2

3 public void UseWithExpression ()


4 {
5 var person1 = new Person ( " John " , " Doe " ) ;
6 var person2 = person1 with { LastName = " Smith " };
7

8 Console . WriteLine ( person2 ) ; // Outputs : Person { FirstName = John , LastName =


Smith }
9 }

What are ‘file-scoped namespaces‘ introduced in C# 10.0, and how do


they simplify code structure?
File-scoped namespaces in C# 10.0 allow you to declare a namespace for an entire file without
wrapping the code in curly braces. This reduces nesting and improves readability, especially in
large files.

Listing 9.78: Code example

1 // Before C # 10.0
2 namespace MyApp
3 {
4 public class Program
5 {
6 // Class implementation
7 }
8 }
9

10 // After C # 10.0: File - scoped namespace


11 namespace MyApp ;
12

13 public class Program


14 {
15 // Class implementation
16 }

410
9.4. New Features and Enhancements in .NET

What is the ‘global using‘ directive introduced in C# 10.0, and how does
it improve code reuse?
The ‘global using‘ directive allows you to declare a ‘using‘ statement once at a project level, so
it’s available globally across all files in the project. This reduces repetitive ‘using‘ declarations in
individual files and improves code reuse.

Listing 9.79: Code example

1 // GlobalUsings . cs ( applies to the whole project )


2 global using System ;
3 global using System . Collections . Generic ;
4

5 // No need to import these in individual files


6 public class Program
7 {
8 public void UseGlobalUsings ()
9 {
10 List < string > names = new List < string >() ;
11 Console . WriteLine ( " Global usings make this easier ! " ) ;
12 }
13 }

How does the ‘null-coalescing assignment‘ operator (??=) work, and when
would you use it?
The null-coalescing assignment operator (??=) assigns a value to a variable if the variable is
currently null. This is useful for simplifying null checks and assignments in one statement.

Listing 9.80: Code example

1 public void UseNullCoalescingAssignment ()


2 {
3 List < string >? names = null ;
4

5 // If names is null , assign a new list


6 names ??= new List < string >() ;
7

8 Console . WriteLine ( names . Count ) ; // Outputs : 0


9 }

411
9.4. New Features and Enhancements in .NET

What is the ‘switch expression‘ introduced in C# 8.0, and how does it


simplify pattern matching?
The ‘switch expression‘ in C# 8.0 is a more concise and functional way to express conditional logic
compared to the traditional ‘switch‘ statement. It supports pattern matching and returns values
directly, making it easier to read and write.

Listing 9.81: Code example

1 // Traditional switch statement


2 public string GetDayOfWeek ( int day )
3 {
4 switch ( day )
5 {
6 case 1: return " Monday " ;
7 case 2: return " Tuesday " ;
8 default : return " Unknown " ;
9 }
10 }
11

12 // Switch expression
13 public string GetDayOfWeek ( int day ) = > day switch
14 {
15 1 = > " Monday " ,
16 2 = > " Tuesday " ,
17 _ = > " Unknown "
18 };

How does the ‘async‘ stream feature (‘IAsyncEnumerable<T>‘) in C#


8.0 improve performance?
‘IAsyncEnumerable<T>‘ allows asynchronous iteration over a collection of data, enabling you to
stream data asynchronously. This improves performance by not requiring the entire data set to be
loaded into memory at once, especially for large data sets.

Listing 9.82: Code example

1 public async IAsyncEnumerable < int > GetNumbersAsync ()


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

412
9.4. New Features and Enhancements in .NET

10 public async Task UseAsyncStream ()


11 {
12 await foreach ( var number in GetNumbersAsync () )
13 {
14 Console . WriteLine ( number ) ; // Numbers are streamed asynchronously
15 }
16 }

How does the ‘default interface methods‘ feature in C# 8.0 help in evolv-
ing interfaces?
Default interface methods allow you to provide default implementations for methods in interfaces.
This enables you to add new methods to interfaces without breaking existing implementations,
making it easier to evolve APIs over time.

Listing 9.83: Code example

1 public interface ILogger


2 {
3 void Log ( string message ) ;
4

5 // Default implementation for LogError


6 void LogError ( string error ) = > Log ( $ " Error : { error } " ) ;
7 }
8

9 public class ConsoleLogger : ILogger


10 {
11 public void Log ( string message )
12 {
13 Console . WriteLine ( message ) ;
14 }
15 }

What are target-typed new expressions introduced in C# 9.0, and how


do they improve code clarity?
Target-typed ‘new‘ expressions allow you to omit the type on the right-hand side of an object
creation expression when the type can be inferred from the context. This reduces redundancy and
makes the code cleaner.

Listing 9.84: Code example

1 // Before C # 9.0
2 List < int > numbers = new List < int >() ;
3

413
9.4. New Features and Enhancements in .NET

4 // After C # 9.0: Target - typed new expression


5 List < int > numbers = new () ;

How does ‘static local functions‘ in C# 8.0 improve performance in cer-


tain scenarios?
Static local functions prevent capturing local variables or instance state, reducing memory alloca-
tion and improving performance. They are especially useful when the local function does not need
access to any variables from the enclosing method.

Listing 9.85: Code example

1 public int Factorial ( int n )


2 {
3 // Static local function to improve performance
4 static int ComputeFactorial ( int number )
5 {
6 if ( number <= 1) return 1;
7 return number * ComputeFactorial ( number - 1) ;
8 }
9

10 return ComputeFactorial ( n ) ;
11 }

How do ‘covariant‘ and ‘contravariant‘ generic type parameters enhance


flexibility in C#?
Covariant (‘out‘) and contravariant (‘in‘) type parameters allow you to be more flexible with type
inheritance in generics. Covariance allows you to return more derived types, and contravariance
allows you to accept more generic types.

Listing 9.86: Code example

1 // Covariant example ( out keyword )


2 public interface ICovariant < out T >
3 {
4 T GetValue () ;
5 }
6

7 // Contravariant example ( in keyword )


8 public interface IContravariant < in T >
9 {
10 void SetValue ( T value ) ;
11 }

414
9.4. New Features and Enhancements in .NET

What is the ‘nint‘ and ‘nuint‘ type introduced in C# 9.0, and when
should you use it?
‘nint‘ and ‘nuint‘ are platform-specific integer types introduced in C# 9.0. They represent a signed
or unsigned integer whose size depends on the platform (32-bit or 64-bit). These types are useful
for low-level interop scenarios where you need to work with platform-specific native pointers.

Listing 9.87: Code example

1 public void UseNint ()


2 {
3 nint platformSpecificInt = 10;
4 nuint platformSpecificUInt = 20;
5

6 Console . WriteLine ( $ " nint : { platformSpecificInt } , nuint : { platformSpecificUInt }


");
7 }

What is the purpose of the ‘not‘ pattern in C# 9.0, and how does it
enhance pattern matching?
The ‘not‘ pattern in C# 9.0 allows you to negate any pattern. It enhances pattern matching by
enabling you to express conditions where a specific pattern should not match, leading to more
readable and concise code.

Listing 9.88: Code example

1 public string ClassifyNumber ( int number ) = > number switch


2 {
3 not 0 = > " Non - zero number " ,
4 _ = > " Zero "
5 };

How does the ‘half-open range‘ (..) operator introduced in C# 8.0 sim-
plify working with arrays and strings?
The half-open range operator (‘..‘) allows you to create ranges for slicing arrays or strings. It
simplifies the syntax for accessing subsets of data by providing a clear and concise way to specify
start and end indices.

Listing 9.89: Code example

1 public void UseRangeOperator ()


2 {
3 int [] numbers = { 1 , 2 , 3 , 4 , 5 , 6 };

415
9.4. New Features and Enhancements in .NET

5 int [] subArray = numbers [1..4]; // Extracts elements from index 1 to 3


6 Console . WriteLine ( string . Join ( " , " , subArray ) ) ; // Outputs : 2 , 3 , 4
7 }

What are ‘nullable reference types‘ in C# 8.0, and how do they prevent
null reference exceptions?
Nullable reference types in C# 8.0 allow you to explicitly declare whether a reference type can be
null or not. This feature helps prevent null reference exceptions by enforcing nullability checks at
compile-time, making your code more robust and safer.

Listing 9.90: Code example

1 # nullable enable
2 public void UseNullableReferenceTypes ()
3 {
4 string ? nullableString = null ; // Nullable reference type
5 string nonNullableString = " Hello " ; // Non - nullable reference type
6

7 // Nullable reference types help avoid null reference exceptions


8 if ( nullableString != null )
9 {
10 Console . WriteLine ( nullableString . Length ) ; // Safe to call
11 }
12 }

How do ‘lambda discard parameters‘ (C# 9.0) improve the readability


of lambdas?
Lambda discard parameters (‘_‘) allow you to explicitly ignore unused parameters in lambda
expressions. This improves readability by making it clear which parameters are intentionally
ignored.

Listing 9.91: Code example

1 public void UseLambdaDiscard ()


2 {
3 Action < int , int > action = (_ , y ) = > Console . WriteLine ( y ) ;
4 action (5 , 10) ; // Outputs : 10 ( ignores the first parameter )
5 }

416
9.4. New Features and Enhancements in .NET

How does the ‘is‘ keyword with patterns enhance type checks and casting
in C# 9.0?
The ‘is‘ keyword combined with patterns simplifies type checking and casting by allowing you to
match types and patterns directly in a single expression. This makes code more readable and
reduces the need for separate type checks and casts.

Listing 9.92: Code example

1 public void UseIsWithPatterns ( object shape )


2 {
3 if ( shape is Circle { Radius : > 0 } c )
4 {
5 Console . WriteLine ( $ " Circle with radius { c . Radius } " ) ;
6 }
7 }

What is ‘top-level statements‘ in C# 9.0, and how do they simplify entry-


point code?
Top-level statements in C# 9.0 allow you to write code directly in the global scope without
needing to explicitly define a ‘Main‘ method. This feature simplifies small applications or script-
like programs by reducing boilerplate code.

Listing 9.93: Code example

1 // No need for a Main method in C # 9.0


2 Console . WriteLine ( " Hello , World ! " ) ;

How do records improve pattern matching in C# 9.0?


Records in C# 9.0 integrate seamlessly with pattern matching, allowing for positional deconstruc-
tion and easy comparison of object properties. This makes pattern matching more expressive and
easier to use when working with immutable data types.

Listing 9.94: Code example

1 public record Person ( string FirstName , string LastName ) ;


2

3 public void UsePatternMatchingWithRecords ()


4 {
5 var person = new Person ( " John " , " Doe " ) ;
6

7 if ( person is Person ( " John " , _ ) )


8 {
9 Console . WriteLine ( " Matched a person with the first name John " ) ;

417
9.4. New Features and Enhancements in .NET

10 }
11 }

418
10 | System Desing and Architecture

System design and architecture are crucial for building scalable, maintainable, and resilient .NET
applications. These concepts involve making high-level decisions about how components interact,
how responsibilities are divided, and how data flows through the system. Understanding archi-
tectural patterns like layered architecture, microservices, and event-driven design is essential for
developing robust enterprise applications.

In job interviews, especially for senior or leadership roles, candidates are often asked to design
a system from scratch or critique an existing one. Interviewers look for an understanding of trade-
offs, such as balancing performance with maintainability, or choosing between synchronous and
asynchronous communication.

Demonstrating strong system design skills shows that you can think beyond writing code—you
can architect solutions that scale with user demand, adapt to new requirements, and support
long-term growth. This capability is highly valued by employers who seek engineers capable of
contributing to strategic technical decisions.

10.1 Low-Level and High-Level System Design


What is the difference between low-level and high-level system design?
Low-level design focuses on the detailed implementation of components within a system, such as
classes, modules, and methods. High-level design, on the other hand, deals with the architecture
of the system, including component interactions, data flow, and system topology.

Listing 10.1: Code example

1 // Example : High - level design for a microservice - based architecture


2 // Service interfaces for interacting between modules
3 public interface IOrderService
4 {
5 void PlaceOrder ( Order order ) ;
6 }

419
10.1. Low-Level and High-Level System Design

8 public interface IInventoryService


9 {
10 bool CheckStock ( int productId ) ;
11 }
12

13 // Low - level design for one of the services ( OrderService )


14 public class OrderService : IOrderService
15 {
16 private readonly IInventoryService _inventoryService ;
17

18 public OrderService ( IInventoryService inventoryService )


19 {
20 _inventoryService = inventoryService ;
21 }
22

23 public void PlaceOrder ( Order order )


24 {
25 if ( _inventoryService . CheckStock ( order . ProductId ) )
26 {
27 // Process order placement
28 }
29 }
30 }

How do you design a scalable web application? What architectural prin-


ciples are important?
To design a scalable web application, principles like load balancing, database partitioning, horizon-
tal scaling, caching, and microservices architecture are important. These principles help distribute
traffic, reduce bottlenecks, and ensure the system scales efficiently as demand grows.

Listing 10.2: Code example

1 // Example : High - level architecture of a scalable web application using


microservices
2 // Microservices architecture using API Gateway for load balancing
3 // Each service ( Auth , Orders , Payments ) can scale independently
4 public class ApiGateway
5 {
6 public string RouteRequest ( string service )
7 {
8 switch ( service )
9 {
10 case " OrderService " :
11 return " Route to Order Service " ;

420
10.1. Low-Level and High-Level System Design

12 case " PaymentService " :


13 return " Route to Payment Service " ;
14 default :
15 return " Service not available " ;
16 }
17 }
18 }

How would you design a high-performance cache for a large-scale system?


A high-performance cache can be designed using a distributed cache (e.g., Redis, Memcached) to
store frequently accessed data. This reduces the load on the database by allowing retrieval of data
from memory. Cache invalidation strategies like TTL (Time-to-Live) and cache eviction policies
(LRU, LFU) are also essential.

Listing 10.3: Code example

1 // Example : Using Redis cache in C #


2 public class CacheService
3 {
4 private readonly ConnectionMultiplexer _redis ;
5

6 public CacheService ()
7 {
8 _redis = ConnectionMultiplexer . Connect ( " localhost " ) ;
9 }
10

11 public void SetCacheValue ( string key , string value )


12 {
13 var db = _redis . GetDatabase () ;
14 db . StringSet ( key , value , TimeSpan . FromMinutes (10) ) ; // Set TTL for 10
minutes
15 }
16

17 public string GetCacheValue ( string key )


18 {
19 var db = _redis . GetDatabase () ;
20 return db . StringGet ( key ) ;
21 }
22 }

What considerations must be made when designing a system for eventual


consistency?
When designing a system for eventual consistency, you must consider:

421
10.1. Low-Level and High-Level System Design

• Data replication across multiple nodes


• Conflict resolution strategies for conflicting updates
• Handling stale data by setting expectations with clients
• Tolerating temporary inconsistency while achieving global eventual consistency

Listing 10.4: Code example

1 // Example : Eventual consistency in a distributed system using a message queue


2 public class OrderService
3 {
4 private readonly IMessageQueue _messageQueue ;
5

6 public OrderService ( IMessageQueue messageQueue )


7 {
8 _messageQueue = messageQueue ;
9 }
10

11 public void PlaceOrder ( Order order )


12 {
13 // Send order to queue for eventual processing
14 _messageQueue . Enqueue ( order ) ;
15 }
16 }
17

18 // Consumer service will process the order asynchronously later


19 public class OrderConsumer
20 {
21 public void ProcessQueue ()
22 {
23 // Process orders from the message queue and update database
24 }
25 }

How would you design a system that supports high availability?


High availability is achieved through redundancy and fault tolerance. Key techniques include:
• Load balancers to distribute traffic across multiple servers
• Replication of data across different regions or availability zones
• Failover mechanisms to switch to backup servers during downtime
• Using databases with high availability features, such as SQL Server Always On or Cassandra

Listing 10.5: Code example

1 // Example : High availability with a load balancer distributing traffic


2 public class LoadBalancer
3 {

422
10.1. Low-Level and High-Level System Design

4 private readonly List < string > _servers = new List < string > { " Server1 " , "
Server2 " , " Server3 " };
5 private int _currentIndex = 0;
6

7 public string RouteRequest ()


8 {
9 _currentIndex = ( _currentIndex + 1) % _servers . Count ;
10 return _servers [ _currentIndex ]; // Round - robin load balancing
11 }
12 }

How do you approach designing a fault-tolerant system?


Fault tolerance can be achieved through redundancy and graceful degradation. Components should
have backup systems, and systems should detect and handle failures automatically. Circuit break-
ers, retries, and fallback mechanisms help ensure the system remains operational even if some
components fail.

Listing 10.6: Code example

1 // Example : Circuit breaker pattern for fault tolerance in C #


2 public class CircuitBreaker
3 {
4 private int _failureCount = 0;
5 private readonly int _threshold = 5;
6

7 public bool IsOpen = > _failureCount >= _threshold ;


8

9 public void Execute ( Action action )


10 {
11 if ( IsOpen )
12 {
13 throw new InvalidOperationException ( " Circuit is open " ) ;
14 }
15

16 try
17 {
18 action () ;
19 _failureCount = 0; // Reset on success
20 }
21 catch ( Exception )
22 {
23 _failureCount ++;
24 if ( IsOpen )
25 {
26 Console . WriteLine ( " Circuit opened due to repeated failures " ) ;

423
10.1. Low-Level and High-Level System Design

27 }
28 throw ;
29 }
30 }
31 }

How would you design a microservices architecture? What are the pros
and cons?
A microservices architecture is designed by splitting an application into loosely coupled services,
each responsible for specific functionality. Services communicate via APIs or message brokers.
The pros are scalability, independent deployments, and resilience. The cons include complexity in
service orchestration and monitoring.

Listing 10.7: Code example

1 // Example : Microservice communication using HTTP and message queue


2 public class OrderService
3 {
4 private readonly HttpClient _httpClient ;
5

6 public OrderService ( HttpClient httpClient )


7 {
8 _httpClient = httpClient ;
9 }
10

11 public async Task PlaceOrder ( Order order )


12 {
13 // Communicate with InventoryService using HTTP
14 var response = await _httpClient . PostAsync ( " https :// inventory / api / check " ,
new StringContent ( order . ProductId ) ) ;
15 if ( response . IsSuccessStatusCode )
16 {
17 // Process order
18 }
19 }
20 }

How do you handle inter-service communication in microservices?


Inter-service communication can be handled using:
• Synchronous HTTP/REST or gRPC for real-time communication
• Asynchronous communication using message brokers (e.g., RabbitMQ, Kafka)
• Event-driven communication for decoupled services

424
10.1. Low-Level and High-Level System Design

Each approach has trade-offs in terms of latency, complexity, and fault tolerance.

Listing 10.8: Code example

1 // Example : Asynchronous communication using RabbitMQ


2 public class OrderPublisher
3 {
4 private readonly IModel _channel ;
5

6 public OrderPublisher ( IConnection connection )


7 {
8 _channel = connection . CreateModel () ;
9 _channel . QueueDeclare ( " OrderQueue " , false , false , false , null ) ;
10 }
11

12 public void PublishOrder ( Order order )


13 {
14 var body = Encoding . UTF8 . GetBytes ( JsonConvert . SerializeObject ( order ) ) ;
15 _channel . BasicPublish ( " " , " OrderQueue " , null , body ) ; // Publish to queue
16 }
17 }

How would you design a distributed system to handle millions of requests


per second?
Designing for high throughput requires:
• Horizontal scaling with load balancing
• Distributed databases or sharded architectures
• Caching with systems like Redis or CDN for static content
• Asynchronous processing to avoid blocking the main request thread

Listing 10.9: Code example

1 // Example : Load balancing millions of requests with distributed cache


2 public class HighThroughputService
3 {
4 private readonly IDistributedCache _cache ;
5

6 public HighThroughputService ( IDistributedCache cache )


7 {
8 _cache = cache ;
9 }
10

11 public async Task < string > HandleRequestAsync ( string key )


12 {
13 var cachedValue = await _cache . GetStringAsync ( key ) ;
14 if (! string . IsNullOrEmpty ( cachedValue ) )

425
10.1. Low-Level and High-Level System Design

15 {
16 return cachedValue ; // Return cached response for high throughput
17 }
18

19 // Handle request and store result in cache


20 var result = await ProcessRequestAsync ( key ) ;
21 await _cache . SetStringAsync ( key , result ) ;
22 return result ;
23 }
24 }

How would you design a system that guarantees low-latency response


times?
To guarantee low-latency, techniques include:
• Using in-memory databases (e.g., Redis) for fast data access
• Avoiding synchronous I/O operations by leveraging asynchronous processing
• Minimizing network hops by placing services close to each other or using CDNs

Listing 10.10: Code example

1 // Example : Asynchronous processing to reduce latency


2 public async Task < string > FetchDataAsync ()
3 {
4 using ( var client = new HttpClient () )
5 {
6 var response = await client . GetStringAsync ( " https :// api . service . com / data " )
;
7 return response ; // Asynchronous network call to reduce latency
8 }
9 }

How do you design a system for data consistency across distributed


databases?
To ensure data consistency in distributed databases, techniques like:
• 2PC (Two-Phase Commit) for strong consistency
• Eventual consistency using techniques like conflict resolution, quorum reads/writes
• Using databases with built-in distributed consistency models (e.g., Cassandra, Cosmos DB)

Listing 10.11: Code example

1 // Example : Eventual consistency with asynchronous message handling


2 public class EventService
3 {

426
10.1. Low-Level and High-Level System Design

4 private readonly IMessageQueue _messageQueue ;


5

6 public EventService ( IMessageQueue messageQueue )


7 {
8 _messageQueue = messageQueue ;
9 }
10

11 public void PublishEvent ( Event evt )


12 {
13 _messageQueue . Enqueue ( evt ) ; // Eventual consistency through message queues
14 }
15

16 public void HandleEvent ()


17 {
18 var evt = _messageQueue . Dequeue () ;
19 // Process the event and update databases
20 }
21 }

How would you design a system to support both online and offline modes
of operation?
Designing for online and offline modes requires:
• Local storage for offline data (e.g., SQLite, Realm)
• Synchronization logic to update data when the user is back online
• Conflict resolution strategies for handling data changes during offline mode

Listing 10.12: Code example

1 // Example : Offline mode using local storage with synchronization


2 public class OfflineDataService
3 {
4 private readonly IOnlineApiService _apiService ;
5 private readonly ILocalStorage _localStorage ;
6

7 public OfflineDataService ( IOnlineApiService apiService , ILocalStorage


localStorage )
8 {
9 _apiService = apiService ;
10 _localStorage = localStorage ;
11 }
12

13 public async Task SyncDataAsync ()


14 {
15 var localData = _localStorage . GetUnsyncedData () ;
16 if ( localData . Any () )

427
10.1. Low-Level and High-Level System Design

17 {
18 await _apiService . SyncData ( localData ) ; // Sync offline data when back
online
19 }
20 }
21 }

How would you design a messaging system to ensure reliable message


delivery?
A reliable messaging system can be designed using message queues (e.g., RabbitMQ, Kafka) with:
• Message persistence to disk to prevent data loss
• Acknowledgment mechanisms to ensure message delivery
• Retry mechanisms for failed deliveries

Listing 10.13: Code example

1 // Example : Reliable messaging with acknowledgments in RabbitMQ


2 public class ReliableMessageHandler
3 {
4 private readonly IModel _channel ;
5

6 public ReliableMessageHandler ( IConnection connection )


7 {
8 _channel = connection . CreateModel () ;
9 _channel . QueueDeclare ( " ReliableQueue " , durable : true , exclusive : false ,
autoDelete : false , arguments : null ) ;
10 }
11

12 public void HandleMessages ()


13 {
14 var consumer = new EventingBasicConsumer ( _channel ) ;
15 consumer . Received += ( model , ea ) = >
16 {
17 var body = ea . Body . ToArray () ;
18 var message = Encoding . UTF8 . GetString ( body ) ;
19

20 try
21 {
22 // Process message
23 _channel . BasicAck ( ea . DeliveryTag , multiple : false ) ; // Acknowledge
successful processing
24 }
25 catch ( Exception )
26 {

428
10.1. Low-Level and High-Level System Design

27 _channel . BasicNack ( ea . DeliveryTag , multiple : false , requeue : true )


; // Retry on failure
28 }
29 };
30 _channel . BasicConsume ( queue : " ReliableQueue " , autoAck : false , consumer :
consumer ) ;
31 }
32 }

How do you design a multi-tenant system? What are the key considera-
tions?
A multi-tenant system allows multiple clients (tenants) to share the same infrastructure while
keeping their data isolated. Key considerations include:
• Tenant isolation (separate databases vs shared databases with tenant IDs)
• Security for tenant data
• Customizability per tenant without affecting others

Listing 10.14: Code example

1 // Example : Multi - tenant system with shared database and tenant IDs
2 public class TenantService
3 {
4 private readonly MyDbContext _context ;
5

6 public TenantService ( MyDbContext context )


7 {
8 _context = context ;
9 }
10

11 public List < Order > GetOrdersForTenant ( int tenantId )


12 {
13 return _context . Orders . Where ( o = > o . TenantId == tenantId ) . ToList () ; //
Filter by tenant ID for isolation
14 }
15 }

How would you design an authentication system using OAuth2?


An OAuth2 authentication system would include:
• Authorization server to handle token issuance
• Client application to obtain access tokens
• Resource server to verify and accept tokens for protected resources

429
10.2. Non-Functional Requirements System Design

OAuth2 includes flows like authorization code flow (for web apps) and implicit flow (for mobile
apps).

Listing 10.15: Code example

1 // Example : OAuth2 token validation in an API


2 public class AuthMiddleware
3 {
4 private readonly RequestDelegate _next ;
5

6 public AuthMiddleware ( RequestDelegate next )


7 {
8 _next = next ;
9 }
10

11 public async Task InvokeAsync ( HttpContext context )


12 {
13 var token = context . Request . Headers [ " Authorization " ]. ToString () . Replace ( "
Bearer " , " " ) ;
14 if (! ValidateToken ( token ) )
15 {
16 context . Response . StatusCode = 401; // Unauthorized
17 return ;
18 }
19

20 await _next ( context ) ; // Proceed if valid


21 }
22

23 private bool ValidateToken ( string token )


24 {
25 // Validate the OAuth2 token with an authorization server
26 return true ; // Simplified for example
27 }
28 }

10.2 Non-Functional Requirements System Design


How would you design a system to ensure high availability?
High availability can be ensured through redundancy and fault tolerance by implementing load
balancing, automatic failover, and data replication across different regions or availability zones. It’s
essential to minimize single points of failure and ensure quick recovery from hardware or software
failures.

Listing 10.16: Code example

430
10.2. Non-Functional Requirements System Design

1 // Example : High availability with load balancing


2 public class LoadBalancer
3 {
4 private readonly List < string > _servers = new List < string > { " Server1 " , "
Server2 " , " Server3 " };
5 private int _currentIndex = 0;
6

7 public string RouteRequest ()


8 {
9 _currentIndex = ( _currentIndex + 1) % _servers . Count ;
10 return _servers [ _currentIndex ]; // Round - robin load balancing
11 }
12 }

How do you design for scalability in a system?


Scalability can be achieved through horizontal scaling (adding more instances of services) and
vertical scaling (increasing server capacity). Utilizing microservices, load balancers, and sharded
or distributed databases enables scaling based on the application’s needs.

Listing 10.17: Code example

1 // Example : Scalable design with a distributed cache ( Redis )


2 public class CacheService
3 {
4 private readonly ConnectionMultiplexer _redis ;
5

6 public CacheService ()
7 {
8 _redis = ConnectionMultiplexer . Connect ( " localhost " ) ;
9 }
10

11 public async Task SetCacheValue ( string key , string value )


12 {
13 var db = _redis . GetDatabase () ;
14 await db . StringSetAsync ( key , value ) ; // Use Redis for caching across
distributed instances
15 }
16

17 public async Task < string > GetCacheValue ( string key )


18 {
19 var db = _redis . GetDatabase () ;
20 return await db . StringGetAsync ( key ) ; // Fetch cached value
21 }
22 }

431
10.2. Non-Functional Requirements System Design

How do you ensure fault tolerance in a distributed system?


Fault tolerance is achieved by introducing redundancy, having failover strategies, and implementing
techniques such as replication, partitioning, and retry mechanisms. Circuit breakers and data
replication are also essential to ensure that the system continues to function even when components
fail.

Listing 10.18: Code example

1 // Example : Circuit breaker pattern to ensure fault tolerance


2 public class CircuitBreaker
3 {
4 private int _failureCount = 0;
5 private readonly int _threshold = 5;
6

7 public bool IsOpen = > _failureCount >= _threshold ;


8

9 public void Execute ( Action action )


10 {
11 if ( IsOpen )
12 {
13 throw new InvalidOperationException ( " Circuit is open " ) ;
14 }
15

16 try
17 {
18 action () ;
19 _failureCount = 0; // Reset on success
20 }
21 catch
22 {
23 _failureCount ++;
24 if ( IsOpen )
25 {
26 Console . WriteLine ( " Circuit opened due to repeated failures " ) ;
27 }
28 throw ;
29 }
30 }
31 }

How would you design a system to meet performance requirements?


Performance can be optimized by minimizing database queries, using caching (e.g., Redis), op-
timizing algorithms, and reducing latency through asynchronous operations and load balancing.
Monitoring and profiling tools should also be used to identify and resolve bottlenecks.

432
10.2. Non-Functional Requirements System Design

Listing 10.19: Code example

1 // Example : Performance optimization with asynchronous I / O operations


2 public async Task < string > FetchDataAsync ()
3 {
4 using ( var client = new HttpClient () )
5 {
6 var response = await client . GetStringAsync ( " https :// api . service . com / data " )
;
7 return response ; // Asynchronous network call to reduce latency
8 }
9 }

How do you design a system for disaster recovery?


Disaster recovery can be achieved by implementing regular backups, database replication across
regions, and failover strategies. Disaster recovery plans should also include Recovery Time Ob-
jective (RTO) and Recovery Point Objective (RPO) to determine acceptable downtime and data
loss.

Listing 10.20: Code example

1 // Example : Disaster recovery with database backup


2 public class BackupService
3 {
4 public void BackupDatabase ( string connectionString )
5 {
6 // Logic to back up database periodically
7 Console . WriteLine ( " Database backup initiated " ) ;
8 }
9

10 public void RestoreDatabase ( string backupPath )


11 {
12 // Logic to restore database from backup
13 Console . WriteLine ( " Database restored from backup " ) ;
14 }
15 }

How would you ensure security in a system?


Security can be enforced through encryption, secure authentication mechanisms (e.g., OAuth2,
JWT), proper access control (RBAC), and following the principle of least privilege. Ensuring
secure communication between services (e.g., HTTPS) and using tools for vulnerability scanning
are also important.

Listing 10.21: Code example

433
10.2. Non-Functional Requirements System Design

1 // Example : Secure JWT authentication in a system


2 public class JwtService
3 {
4 public string GenerateToken ( string userId )
5 {
6 var tokenHandler = new JwtSecurityTokenHandler () ;
7 var key = Encoding . ASCII . GetBytes ( " secretkey " ) ;
8 var tokenDescriptor = new SecurityTokenDescriptor
9 {
10 Subject = new ClaimsIdentity ( new [] { new Claim ( " id " , userId ) }) ,
11 Expires = DateTime . UtcNow . AddHours (1) ,
12 SigningCredentials = new SigningCredentials ( new SymmetricSecurityKey (
key ) , SecurityAlgorithms . HmacSha256Signature )
13 };
14 var token = tokenHandler . CreateToken ( tokenDescriptor ) ;
15 return tokenHandler . WriteToken ( token ) ; // Return JWT token
16 }
17 }

How do you ensure system maintainability and reduce technical debt?


To ensure maintainability, modular and clean code practices should be followed. Implementing
unit tests, code reviews, and continuous refactoring helps prevent technical debt. Documentation
and adherence to SOLID principles also contribute to system maintainability.
Listing 10.22: Code example

1 // Example : Clean and modular design for maintainability


2 public interface IOrderService
3 {
4 void ProcessOrder ( Order order ) ;
5 }
6

7 public class OrderService : IOrderService


8 {
9 private readonly IInventoryService _inventoryService ;
10

11 public OrderService ( IInventoryService inventoryService )


12 {
13 _inventoryService = inventoryService ;
14 }
15

16 public void ProcessOrder ( Order order )


17 {
18 if ( _inventoryService . CheckStock ( order . ProductId ) )
19 {
20 // Process order

434
10.2. Non-Functional Requirements System Design

21 }
22 }
23 }

How would you design a system to meet regulatory compliance (e.g.,


GDPR)?
Regulatory compliance can be ensured by encrypting sensitive data, implementing data access
controls, and logging data access for auditing purposes. For GDPR, additional measures like user
consent for data usage, right to be forgotten, and data anonymization are required.
Listing 10.23: Code example

1 // Example : Ensuring compliance with GDPR by anonymizing user data


2 public class GdprService
3 {
4 public void AnonymizeUserData ( User user )
5 {
6 user . Name = null ;
7 user . Email = null ;
8 user . Phone = null ;
9 // Other PII ( Personally Identifiable Information ) anonymized
10 SaveAnonymizedData ( user ) ;
11 }
12

13 private void SaveAnonymizedData ( User user )


14 {
15 // Save anonymized data to the database
16 }
17 }

How do you design a system for logging and monitoring?


Logging and monitoring are implemented using centralized logging systems (e.g., ELK stack,
Splunk) and monitoring tools (e.g., Prometheus, Grafana). Logs should include error details,
user actions, and performance metrics to track system health and usage.
Listing 10.24: Code example

1 // Example : Basic logging using Serilog in C #


2 var log = new LoggerConfiguration ()
3 . WriteTo . Console ()
4 . CreateLogger () ;
5

6 log . Information ( " This is a log entry " ) ; // Log information


7 log . Error ( " This is an error log entry " ) ; // Log error

435
10.2. Non-Functional Requirements System Design

How do you design a system for ease of deployment and Continuous


Integration/Continuous Delivery (CI/CD)?
To ensure ease of deployment and CI/CD, automated pipelines (e.g., Jenkins, GitLab CI) should be
used. These pipelines automate the build, test, and deployment process. Containerization (e.g.,
Docker) and orchestration tools (e.g., Kubernetes) also help in deploying systems consistently
across environments.

Listing 10.25: Code example

1 // Example : CI / CD pipeline with Docker and Jenkins


2 pipeline {
3 agent any
4

5 stages {
6 stage ( ’ Build ’) {
7 steps {
8 sh ’ dotnet build ’
9 }
10 }
11

12 stage ( ’ Test ’) {
13 steps {
14 sh ’ dotnet test ’
15 }
16 }
17

18 stage ( ’ Docker Build and Push ’) {


19 steps {
20 script {
21 docker . build ( ’ myapp ’) . push ( ’ myregistry / myapp ’)
22 }
23 }
24 }
25

26 stage ( ’ Deploy ’) {
27 steps {
28 sh ’ kubectl apply -f deployment . yaml ’
29 }
30 }
31 }
32 }

436
10.2. Non-Functional Requirements System Design

How do you ensure a system is reliable and stable?


Reliability can be achieved by performing stress tests, using health checks, and deploying redundant
systems. Monitoring tools should track uptime, latency, and error rates to detect issues early and
ensure stability.

Listing 10.26: Code example

1 // Example : Implementing a health check endpoint in an API


2 [ ApiController ]
3 [ Route ( " api /[ controller ] " ) ]
4 public class HealthCheckController : ControllerBase
5 {
6 [ HttpGet ]
7 public IActionResult CheckHealth ()
8 {
9 // Perform system checks here
10 return Ok ( " System is healthy " ) ; // Return 200 if system is healthy
11 }
12 }

How would you handle system updates and patch management without
causing downtime?
To ensure zero downtime during updates, rolling deployments and blue-green deployment strategies
can be used. In blue-green deployments, traffic is routed to an updated environment while the
previous one remains live as a backup. For rolling deployments, instances are updated one at a
time to avoid service disruption.

Listing 10.27: Code example

1 // Example : Blue - green deployment logic with traffic switching


2 public class TrafficRouter
3 {
4 private string _currentEnvironment = " Green " ; // Default environment
5

6 public void SwitchToBlue ()


7 {
8 _currentEnvironment = " Blue " ; // Switch traffic to Blue environment
9 }
10

11 public string GetEnvironment ()


12 {
13 return _currentEnvironment ;
14 }
15 }

437
10.2. Non-Functional Requirements System Design

How do you design a system to meet accessibility requirements?


Accessibility requirements can be met by following accessibility standards (e.g., WCAG), ensuring
that the user interface is usable by assistive technologies (e.g., screen readers), and providing
keyboard navigability, proper color contrast, and alternative text for images.

Listing 10.28: Code example

1 // Example : Adding accessibility features to a web form


2 public class AccessibleForm : Controller
3 {
4 public IActionResult Index ()
5 {
6 // Ensure form is accessible with proper labels and ARIA attributes
7 return View () ;
8 }
9 }

How do you design a system to handle large amounts of data (big data)?
For handling big data, distributed data processing systems like Hadoop, Spark, or cloud-based
solutions like AWS Redshift are used. Data partitioning, indexing, and sharding are employed to
handle large volumes of data efficiently.

Listing 10.29: Code example

1 // Example : Using parallel processing with large data sets


2 public class BigDataProcessor
3 {
4 public void ProcessLargeDataSet ( List < Data > dataSet )
5 {
6 Parallel . ForEach ( dataSet , data = >
7 {
8 // Process data in parallel to handle large volumes
9 }) ;
10 }
11 }

How would you design a system to ensure low-latency data access?


To ensure low-latency data access, use in-memory caching (e.g., Redis), optimize database queries,
and consider geographically distributed data centers to minimize network latency. Asynchronous
processing and queuing can also help reduce latency in request handling.

Listing 10.30: Code example

1 // Example : Using Redis cache to reduce data access latency

438
10.2. Non-Functional Requirements System Design

2 public class LowLatencyService


3 {
4 private readonly IDistributedCache _cache ;
5

6 public LowLatencyService ( IDistributedCache cache )


7 {
8 _cache = cache ;
9 }
10

11 public async Task < string > GetCachedDataAsync ( string key )


12 {
13 return await _cache . GetStringAsync ( key ) ; // Fetch from in - memory cache
14 }
15 }

How do you ensure a system can handle spikes in traffic (elasticity)?


Elasticity can be achieved through auto-scaling mechanisms (e.g., AWS Auto Scaling, Kubernetes
horizontal pod autoscaling) that provision additional resources when traffic increases. Load bal-
ancers should be employed to distribute traffic evenly across instances.

Listing 10.31: Code example

1 // Example : Elastic scaling logic with Kubernetes horizontal scaling


2 public class ElasticService
3 {
4 // Kubernetes automatically scales based on CPU / memory usage or custom metrics
5 }

How do you design a system for fault isolation?


Fault isolation can be achieved by designing systems using microservices or isolated modules where
failure in one part of the system doesn’t propagate to other parts. Circuit breakers, retries, and
separate deployment units also help in isolating faults.

Listing 10.32: Code example

1 // Example : Fault isolation in microservices using circuit breaker pattern


2 public class FaultIsolatedService
3 {
4 private readonly IInventoryService _inventoryService ;
5

6 public FaultIsolatedService ( IInventoryService inventoryService )


7 {
8 _inventoryService = inventoryService ;
9 }

439
10.3. Systems Architecture, Components, Modules, and Layers

10

11 public void ProcessOrder ( Order order )


12 {
13 // Circuit breaker ensures inventory service failure doesn ’t affect
overall system
14 CircuitBreaker . Execute (() = > _inventoryService . CheckStock ( order . ProductId )
);
15 }
16 }

10.3 Systems Architecture, Components, Modules, and Lay-


ers
How would you describe a layered architecture in system design?
A layered architecture separates the concerns of an application into distinct layers (e.g., presen-
tation, business logic, and data access layers). Each layer interacts only with the layer directly
below it, which promotes separation of concerns, maintainability, and scalability.

Listing 10.33: Code example

1 // Example : Layered architecture in C # with business and data access layers


2

3 // Business Logic Layer


4 public class OrderService
5 {
6 private readonly IOrderRepository _orderRepository ;
7

8 public OrderService ( IOrderRepository orderRepository )


9 {
10 _orderRepository = orderRepository ;
11 }
12

13 public void ProcessOrder ( Order order )


14 {
15 // Business rules and logic
16 _orderRepository . Save ( order ) ;
17 }
18 }
19

20 // Data Access Layer


21 public class OrderRepository : IOrderRepository
22 {
23 public void Save ( Order order )
24 {

440
10.3. Systems Architecture, Components, Modules, and Layers

25 // Save order to database


26 }
27 }

What is the difference between monolithic and microservices architec-


ture?
In a monolithic architecture, the entire application is built as a single unit where all components
(UI, business logic, data access) are tightly coupled. Microservices architecture, on the other
hand, breaks the application into independent services, each responsible for a specific functionality,
allowing for independent scaling and deployments.

Listing 10.34: Code example

1 // Example : Microservices architecture using independent services


2

3 // Order Service
4 public class OrderService
5 {
6 public void PlaceOrder ( Order order )
7 {
8 // Logic to handle order placement
9 }
10 }
11

12 // Inventory Service
13 public class InventoryService
14 {
15 public bool CheckStock ( int productId )
16 {
17 // Logic to check product stock
18 return true ;
19 }
20 }

How does a client-server architecture work in system design?


In a client-server architecture, the client makes requests to a central server, which processes the
requests and sends back responses. The server typically hosts the business logic and data, while
the client is responsible for the user interface and sending requests.

Listing 10.35: Code example

1 // Example : Client - server communication using HTTP


2

441
10.3. Systems Architecture, Components, Modules, and Layers

3 // Client - side code


4 public class Client
5 {
6 private readonly HttpClient _httpClient ;
7

8 public Client ( HttpClient httpClient )


9 {
10 _httpClient = httpClient ;
11 }
12

13 public async Task < string > FetchDataAsync ()


14 {
15 var response = await _httpClient . GetStringAsync ( " https :// server / api / data " )
;
16 return response ;
17 }
18 }
19

20 // Server - side code


21 [ ApiController ]
22 [ Route ( " api /[ controller ] " ) ]
23 public class DataController : ControllerBase
24 {
25 [ HttpGet ]
26 public IActionResult GetData ()
27 {
28 return Ok ( " Data from server " ) ;
29 }
30 }

What is an N-tier architecture, and how does it differ from a layered


architecture?
N-tier architecture is a type of layered architecture where each layer (e.g., presentation, business,
data) is physically separated, often running on different machines or environments (tiers). Layered
architecture, on the other hand, refers to logical separation without requiring physical separation.

Listing 10.36: Code example

1 // Example : N - tier architecture with presentation , business , and data tiers


2

3 // Presentation Tier ( Web App )


4 public class OrderController : Controller
5 {
6 private readonly IOrderService _orderService ;
7

442
10.3. Systems Architecture, Components, Modules, and Layers

8 public OrderController ( IOrderService orderService )


9 {
10 _orderService = orderService ;
11 }
12

13 public IActionResult PlaceOrder ( Order order )


14 {
15 _orderService . ProcessOrder ( order ) ;
16 return View () ;
17 }
18 }
19

20 // Business Tier ( Service Layer )


21 public class OrderService : IOrderService
22 {
23 private readonly IOrderRepository _orderRepository ;
24

25 public OrderService ( IOrderRepository orderRepository )


26 {
27 _orderRepository = orderRepository ;
28 }
29

30 public void ProcessOrder ( Order order )


31 {
32 // Business rules
33 _orderRepository . SaveOrder ( order ) ;
34 }
35 }
36

37 // Data Tier ( Data Access Layer )


38 public class OrderRepository : IOrderRepository
39 {
40 public void SaveOrder ( Order order )
41 {
42 // Save order to database
43 }
44 }

How do you handle inter-module communication in a modular architec-


ture?
Inter-module communication in a modular architecture can be handled using techniques like event-
driven architecture (publish-subscribe pattern), service-to-service communication (REST, gRPC),
or shared data repositories. Loose coupling between modules ensures maintainability and scalabil-
ity.

443
10.3. Systems Architecture, Components, Modules, and Layers

Listing 10.37: Code example

1 // Example : Inter - module communication using an event - driven architecture ( publish


- subscribe pattern )
2

3 // Event Publisher ( Order Module )


4 public class OrderModule
5 {
6 private readonly IMessageQueue _messageQueue ;
7

8 public OrderModule ( IMessageQueue messageQueue )


9 {
10 _messageQueue = messageQueue ;
11 }
12

13 public void PlaceOrder ( Order order )


14 {
15 // Publish order event to queue
16 _messageQueue . Publish ( " OrderPlaced " , order ) ;
17 }
18 }
19

20 // Event Subscriber ( Inventory Module )


21 public class InventoryModule
22 {
23 private readonly IMessageQueue _messageQueue ;
24

25 public InventoryModule ( IMessageQueue messageQueue )


26 {
27 _messageQueue = messageQueue ;
28 }
29

30 public void SubscribeToOrderEvents ()


31 {
32 _messageQueue . Subscribe ( " OrderPlaced " , HandleOrderPlaced ) ;
33 }
34

35 private void HandleOrderPlaced ( Order order )


36 {
37 // Handle order placed event and update inventory
38 }
39 }

How do you implement service discovery in a microservices architecture?


Service discovery in microservices architecture allows services to find and communicate with each
other without hardcoding IP addresses. It can be implemented using a service registry (e.g.,

444
10.3. Systems Architecture, Components, Modules, and Layers

Consul, Eureka) where services register themselves and clients query the registry to find service
instances.

Listing 10.38: Code example

1 // Example : Service discovery using Consul


2

3 public class ServiceDiscoveryClient


4 {
5 private readonly HttpClient _httpClient ;
6

7 public ServiceDiscoveryClient ( HttpClient httpClient )


8 {
9 _httpClient = httpClient ;
10 }
11

12 public async Task < string > DiscoverService ( string serviceName )


13 {
14 var response = await _httpClient . GetStringAsync ( $ " http :// consul / v1 / catalog
/ service /{ serviceName } " ) ;
15 return response ; // Get service instances registered in Consul
16 }
17 }

What is the difference between a component and a module in system


architecture?
A component is a smaller, self-contained part of the system responsible for specific functionality,
such as a class or method, while a module is a collection of related components that together provide
a broader functionality or service. Modules are larger organizational units that can encompass
multiple components.

Listing 10.39: Code example

1 // Example : Components and modules


2

3 // Component ( Order Processor )


4 public class OrderProcessor
5 {
6 public void Process ( Order order )
7 {
8 // Process individual order
9 }
10 }
11

12 // Module ( Order Module ) - composed of multiple components


13 public class OrderModule

445
10.3. Systems Architecture, Components, Modules, and Layers

14 {
15 private readonly OrderProcessor _orderProcessor ;
16

17 public OrderModule ( OrderProcessor orderProcessor )


18 {
19 _orderProcessor = orderProcessor ;
20 }
21

22 public void ProcessOrders ( IEnumerable < Order > orders )


23 {
24 foreach ( var order in orders )
25 {
26 _orderProcessor . Process ( order ) ; // Use component within module
27 }
28 }
29 }

How do you design a loosely coupled system?


A loosely coupled system is designed by minimizing direct dependencies between components and
modules. This can be achieved using interfaces, dependency injection, and event-driven architec-
ture to allow modules to interact without tightly coupling their implementation.
Listing 10.40: Code example

1 // Example : Loose coupling using dependency injection


2

3 public interface IInventoryService


4 {
5 bool CheckStock ( int productId ) ;
6 }
7

8 public class OrderService


9 {
10 private readonly IInventoryService _inventoryService ;
11

12 public OrderService ( IInventoryService inventoryService )


13 {
14 _inventoryService = inventoryService ;
15 }
16

17 public void ProcessOrder ( Order order )


18 {
19 if ( _inventoryService . CheckStock ( order . ProductId ) )
20 {
21 // Place order
22 }

446
10.3. Systems Architecture, Components, Modules, and Layers

23 }
24 }

What are the key components in a three-tier architecture?


A three-tier architecture consists of:
• ‘Presentation Layer‘: The user interface or front end.
• ‘Business Logic Layer‘: Handles the core functionality and logic of the system.
• ‘Data Access Layer‘: Manages interactions with the database or data source.

Listing 10.41: Code example

1 // Example : Three - tier architecture


2

3 // Presentation Layer ( Web Controller )


4 public class OrderController : Controller
5 {
6 private readonly IOrderService _orderService ;
7

8 public OrderController ( IOrderService orderService )


9 {
10 _orderService = orderService ;
11 }
12

13 public IActionResult PlaceOrder ( Order order )


14 {
15 _orderService . ProcessOrder ( order ) ;
16 return View () ;
17 }
18 }
19

20 // Business Logic Layer ( Order Service )


21 public class OrderService : IOrderService
22 {
23 private readonly IOrderRepository _orderRepository ;
24

25 public OrderService ( IOrderRepository orderRepository )


26 {
27 _orderRepository = orderRepository ;
28 }
29

30 public void ProcessOrder ( Order order )


31 {
32 // Business logic to process the order
33 _orderRepository . SaveOrder ( order ) ;
34 }

447
10.3. Systems Architecture, Components, Modules, and Layers

35 }
36

37 // Data Access Layer ( Order Repository )


38 public class OrderRepository : IOrderRepository
39 {
40 public void SaveOrder ( Order order )
41 {
42 // Save order to database
43 }
44 }

How does a message queue work in a distributed system?


In a distributed system, a message queue allows asynchronous communication between services or
components. Producers send messages to the queue, and consumers read the messages from the
queue. This decouples services and enables load balancing, fault tolerance, and scalability.
Listing 10.42: Code example

1 // Example : Message queue using RabbitMQ


2

3 public class MessageQueueProducer


4 {
5 private readonly IModel _channel ;
6

7 public MessageQueueProducer ( IConnection connection )


8 {
9 _channel = connection . CreateModel () ;
10 _channel . QueueDeclare ( queue : " order_queue " , durable : false , exclusive :
false , autoDelete : false , arguments : null ) ;
11 }
12

13 public void SendMessage ( Order order )


14 {
15 var body = Encoding . UTF8 . GetBytes ( JsonConvert . SerializeObject ( order ) ) ;
16 _channel . BasicPublish ( exchange : " " , routingKey : " order_queue " ,
basicProperties : null , body : body ) ;
17 }
18 }
19

20 public class MessageQueueConsumer


21 {
22 private readonly IModel _channel ;
23

24 public MessageQueueConsumer ( IConnection connection )


25 {
26 _channel = connection . CreateModel () ;

448
10.3. Systems Architecture, Components, Modules, and Layers

27 }
28

29 public void ConsumeMessages ()


30 {
31 var consumer = new EventingBasicConsumer ( _channel ) ;
32 consumer . Received += ( model , ea ) = >
33 {
34 var body = ea . Body . ToArray () ;
35 var message = Encoding . UTF8 . GetString ( body ) ;
36 Console . WriteLine ( " Received message : " + message ) ;
37 };
38 _channel . BasicConsume ( queue : " order_queue " , autoAck : true , consumer :
consumer ) ;
39 }
40 }

How would you design an event-driven architecture?


An event-driven architecture involves components or services that communicate by producing and
consuming events. Producers publish events to an event broker or message queue, and consumers
subscribe to these events and react to them. This design promotes loose coupling and scalability.
Listing 10.43: Code example

1 // Example : Event - driven architecture using a message broker


2

3 // Event Publisher ( Order Module )


4 public class OrderModule
5 {
6 private readonly IMessageBroker _messageBroker ;
7

8 public OrderModule ( IMessageBroker messageBroker )


9 {
10 _messageBroker = messageBroker ;
11 }
12

13 public void PlaceOrder ( Order order )


14 {
15 // Publish order placed event
16 _messageBroker . Publish ( " OrderPlaced " , order ) ;
17 }
18 }
19

20 // Event Subscriber ( Inventory Module )


21 public class InventoryModule
22 {
23 private readonly IMessageBroker _messageBroker ;

449
10.3. Systems Architecture, Components, Modules, and Layers

24

25 public InventoryModule ( IMessageBroker messageBroker )


26 {
27 _messageBroker = messageBroker ;
28 }
29

30 public void SubscribeToOrderEvents ()


31 {
32 _messageBroker . Subscribe ( " OrderPlaced " , HandleOrderPlaced ) ;
33 }
34

35 private void HandleOrderPlaced ( Order order )


36 {
37 // Handle the order placed event
38 }
39 }

How do you design a system that follows the microkernel architecture


pattern?
The microkernel architecture consists of a core system that handles the most essential functions
and several plug-ins that extend functionality. This allows the system to be highly flexible and
extensible by adding or removing plug-ins without modifying the core.

Listing 10.44: Code example

1 // Example : Microkernel architecture with core functionality and plugins


2

3 // Core system
4 public class CoreSystem
5 {
6 private readonly List < IPlugin > _plugins = new List < IPlugin >() ;
7

8 public void RegisterPlugin ( IPlugin plugin )


9 {
10 _plugins . Add ( plugin ) ;
11 }
12

13 public void ExecutePlugins ()


14 {
15 foreach ( var plugin in _plugins )
16 {
17 plugin . Execute () ;
18 }
19 }
20 }

450
10.3. Systems Architecture, Components, Modules, and Layers

21

22 // Plugin interface
23 public interface IPlugin
24 {
25 void Execute () ;
26 }
27

28 // Plugin implementation
29 public class LoggingPlugin : IPlugin
30 {
31 public void Execute ()
32 {
33 Console . WriteLine ( " Logging plugin executed " ) ;
34 }
35 }

How would you design a scalable data access layer?


A scalable data access layer is designed by implementing connection pooling, using distributed
databases or sharding, and employing caching mechanisms to reduce database load. Asynchronous
database queries and batching are also used to improve scalability.
Listing 10.45: Code example

1 // Example : Scalable data access using async queries


2

3 public class ScalableDataAccessLayer


4 {
5 private readonly DbContext _dbContext ;
6

7 public ScalableDataAccessLayer ( DbContext dbContext )


8 {
9 _dbContext = dbContext ;
10 }
11

12 public async Task < List < Order > > GetOrdersAsync ()
13 {
14 return await _dbContext . Orders . ToListAsync () ; // Async database query for
scalability
15 }
16 }

How do you design a system with a plug-in architecture?


A plug-in architecture allows you to extend the functionality of an application by dynamically
adding plug-ins. The core system defines a set of contracts or interfaces, and plug-ins implement

451
10.3. Systems Architecture, Components, Modules, and Layers

these interfaces to provide additional functionality. This makes the system highly extensible and
modular.

Listing 10.46: Code example

1 // Example : Plug - in architecture with dynamic loading of plugins


2

3 public class PluginManager


4 {
5 private readonly List < IPlugin > _plugins = new List < IPlugin >() ;
6

7 public void LoadPlugins ( string pluginDirectory )


8 {
9 var pluginAssemblies = Directory . GetFiles ( pluginDirectory , " *. dll " ) ;
10 foreach ( var assemblyFile in pluginAssemblies )
11 {
12 var assembly = Assembly . LoadFrom ( assemblyFile ) ;
13 var types = assembly . GetTypes () . Where ( t = > typeof ( IPlugin ) .
IsAssignableFrom ( t ) ) ;
14 foreach ( var type in types )
15 {
16 var plugin = ( IPlugin ) Activator . CreateInstance ( type ) ;
17 _plugins . Add ( plugin ) ;
18 }
19 }
20 }
21

22 public void ExecutePlugins ()


23 {
24 foreach ( var plugin in _plugins )
25 {
26 plugin . Execute () ;
27 }
28 }
29 }
30

31 // Plugin interface
32 public interface IPlugin
33 {
34 void Execute () ;
35 }
36

37 // Plugin implementation
38 public class LoggingPlugin : IPlugin
39 {
40 public void Execute ()
41 {
42 Console . WriteLine ( " Logging plugin executed " ) ;

452
10.3. Systems Architecture, Components, Modules, and Layers

43 }
44 }

How would you design a system using the hexagonal architecture (ports
and adapters)?
Hexagonal architecture, also known as ports and adapters, separates the core business logic from
external concerns like databases, user interfaces, or messaging systems. The core interacts with
the outside world through ports (interfaces), and adapters implement these interfaces to connect
to external systems.

Listing 10.47: Code example

1 // Example : Hexagonal architecture with ports and adapters


2

3 // Port ( interface for order processing )


4 public interface IOrderPort
5 {
6 void ProcessOrder ( Order order ) ;
7 }
8

9 // Core business logic ( uses the port )


10 public class OrderService
11 {
12 private readonly IOrderPort _orderPort ;
13

14 public OrderService ( IOrderPort orderPort )


15 {
16 _orderPort = orderPort ;
17 }
18

19 public void HandleOrder ( Order order )


20 {
21 _orderPort . ProcessOrder ( order ) ; // Use port to interact with external
system
22 }
23 }
24

25 // Adapter ( implements the port to connect to a specific system )


26 public class OrderAdapter : IOrderPort
27 {
28 public void ProcessOrder ( Order order )
29 {
30 // Logic to process the order ( e . g . , send to external API )
31 }
32 }

453
10.4. Architectural Patterns and Message Queues

10.4 Architectural Patterns and Message Queues


What is the microservices architecture pattern, and how does it benefit
large-scale systems?
Microservices architecture divides a large system into small, independent services, each responsible
for a specific business functionality. The benefits include scalability, easier deployment, fault
isolation, and independent development by separate teams. Microservices can communicate using
lightweight protocols such as HTTP, gRPC, or message queues.

Listing 10.48: Code example

1 // Example : Microservice with an API endpoint


2

3 [ ApiController ]
4 [ Route ( " api /[ controller ] " ) ]
5 public class OrderServiceController : ControllerBase
6 {
7 private readonly IOrderProcessor _orderProcessor ;
8

9 public OrderServiceController ( IOrderProcessor orderProcessor )


10 {
11 _orderProcessor = orderProcessor ;
12 }
13

14 [ HttpPost ]
15 public IActionResult PlaceOrder ( Order order )
16 {
17 _orderProcessor . ProcessOrder ( order ) ;
18 return Ok ( " Order placed successfully " ) ;
19 }
20 }

How does the event-driven architecture pattern work, and what advan-
tages does it provide in distributed systems?
Event-driven architecture (EDA) is a design pattern where components produce and consume
events asynchronously. The system responds to these events, which decouples producers and
consumers. This improves scalability and allows for easier modification of components without
disrupting other services.

Listing 10.49: Code example

1 // Example : Event - driven architecture with event publishing and handling


2

454
10.4. Architectural Patterns and Message Queues

3 // Event Publisher
4 public class OrderService
5 {
6 private readonly IMessageQueue _messageQueue ;
7

8 public OrderService ( IMessageQueue messageQueue )


9 {
10 _messageQueue = messageQueue ;
11 }
12

13 public void PlaceOrder ( Order order )


14 {
15 // Publish event when an order is placed
16 _messageQueue . Publish ( " OrderPlaced " , order ) ;
17 }
18 }
19

20 // Event Consumer
21 public class InventoryService
22 {
23 private readonly IMessageQueue _messageQueue ;
24

25 public InventoryService ( IMessageQueue messageQueue )


26 {
27 _messageQueue = messageQueue ;
28 }
29

30 public void SubscribeToOrderEvents ()


31 {
32 _messageQueue . Subscribe ( " OrderPlaced " , ProcessOrder ) ;
33 }
34

35 private void ProcessOrder ( Order order )


36 {
37 // Update inventory based on the placed order
38 }
39 }

How would you implement a message queue in a microservices architec-


ture, and what are the benefits of using message queues?
Message queues in a microservices architecture decouple services, allowing them to communicate
asynchronously. This increases fault tolerance and enables scalability, as services can be scaled
independently. RabbitMQ, Kafka, and Azure Service Bus are popular message queue systems.

455
10.4. Architectural Patterns and Message Queues

Listing 10.50: Code example

1 // Example : Message queue using RabbitMQ


2

3 public class MessageQueueService


4 {
5 private readonly IModel _channel ;
6

7 public MessageQueueService ( IConnection connection )


8 {
9 _channel = connection . CreateModel () ;
10 _channel . QueueDeclare ( queue : " order_queue " , durable : true , exclusive :
false , autoDelete : false , arguments : null ) ;
11 }
12

13 public void PublishOrder ( Order order )


14 {
15 var body = Encoding . UTF8 . GetBytes ( JsonConvert . SerializeObject ( order ) ) ;
16 _channel . BasicPublish ( exchange : " " , routingKey : " order_queue " ,
basicProperties : null , body : body ) ;
17 }
18 }

What is the CQRS (Command Query Responsibility Segregation) pat-


tern, and when would you use it?
CQRS is a pattern where the read and write operations are separated into different models. Com-
mands modify state, while queries read the state. It is useful in systems with complex read and
write patterns, where performance or scalability can be improved by separating concerns.

Listing 10.51: Code example

1 // Example : CQRS pattern with separate read and write models


2

3 // Command model ( writing data )


4 public class OrderCommandHandler
5 {
6 private readonly IOrderRepository _orderRepository ;
7

8 public OrderCommandHandler ( IOrderRepository orderRepository )


9 {
10 _orderRepository = orderRepository ;
11 }
12

13 public void PlaceOrder ( Order order )


14 {
15 // Logic to place order and modify the state

456
10.4. Architectural Patterns and Message Queues

16 _orderRepository . Save ( order ) ;


17 }
18 }
19

20 // Query model ( reading data )


21 public class OrderQueryHandler
22 {
23 private readonly IOrderRepository _orderRepository ;
24

25 public OrderQueryHandler ( IOrderRepository orderRepository )


26 {
27 _orderRepository = orderRepository ;
28 }
29

30 public Order GetOrderById ( int orderId )


31 {
32 // Logic to read data without modifying the state
33 return _orderRepository . GetOrderById ( orderId ) ;
34 }
35 }

How do you implement the Saga pattern in distributed systems, and


what problem does it solve?
The Saga pattern handles long-running transactions across multiple services in distributed sys-
tems. Each service performs its part of the transaction and publishes an event. If any step fails,
compensating actions are taken to undo the previous steps. This helps manage consistency across
microservices.

Listing 10.52: Code example

1 // Example : Saga pattern with distributed transaction and compensating action


2

3 public class OrderSaga


4 {
5 private readonly IPaymentService _paymentService ;
6 private readonly IInventoryService _inventoryService ;
7 private readonly IOrderRepository _orderRepository ;
8

9 public void StartOrderSaga ( Order order )


10 {
11 try
12 {
13 _paymentService . DeductPayment ( order ) ;
14 _inventoryService . ReserveStock ( order ) ;
15 _orderRepository . Save ( order ) ;

457
10.4. Architectural Patterns and Message Queues

16 }
17 catch ( Exception ex )
18 {
19 // Compensating action to rollback transaction
20 _paymentService . Refund ( order ) ;
21 _inventoryService . ReleaseStock ( order ) ;
22 }
23 }
24 }

What is the difference between a message queue and a message broker,


and when would you use each?
A message queue is a simple data structure that stores and forwards messages between producers
and consumers. A message broker manages the message queue and provides additional features
like routing, persistence, and load balancing. Use message queues for direct producer-consumer
communication and brokers for complex routing and communication patterns.

Listing 10.53: Code example

1 // Example : Simple message queue using in - memory queue


2 public class SimpleMessageQueue
3 {
4 private readonly Queue < string > _queue = new Queue < string >() ;
5

6 public void EnqueueMessage ( string message )


7 {
8 _queue . Enqueue ( message ) ;
9 }
10

11 public string DequeueMessage ()


12 {
13 return _queue . Count > 0 ? _queue . Dequeue () : null ;
14 }
15 }

How do you implement retry mechanisms with message queues to handle


failures gracefully?
Retry mechanisms can be implemented by re-queuing messages when a failure occurs. Dead-letter
queues can be used to handle messages that fail repeatedly. This ensures that transient failures
do not lead to message loss, and problematic messages are isolated.

Listing 10.54: Code example

458
10.4. Architectural Patterns and Message Queues

1 // Example : Retry mechanism with RabbitMQ


2

3 public class MessageConsumer


4 {
5 private readonly IModel _channel ;
6

7 public MessageConsumer ( IConnection connection )


8 {
9 _channel = connection . CreateModel () ;
10 }
11

12 public void ConsumeMessage ()


13 {
14 var consumer = new EventingBasicConsumer ( _channel ) ;
15 consumer . Received += ( model , ea ) = >
16 {
17 var messageBody = Encoding . UTF8 . GetString ( ea . Body . ToArray () ) ;
18 try
19 {
20 // Try processing the message
21 ProcessMessage ( messageBody ) ;
22 _channel . BasicAck ( ea . DeliveryTag , false ) ; // Acknowledge
successful processing
23 }
24 catch ( Exception )
25 {
26 _channel . BasicNack ( ea . DeliveryTag , false , true ) ; // Requeue the
message for retry
27 }
28 };
29 _channel . BasicConsume ( queue : " order_queue " , autoAck : false , consumer :
consumer ) ;
30 }
31

32 private void ProcessMessage ( string message )


33 {
34 // Logic to process the message
35 }
36 }

How do you implement the publish-subscribe pattern using a message


broker?
The publish-subscribe pattern allows multiple subscribers to receive messages from a single pub-
lisher. In this pattern, the publisher sends messages to a topic or exchange, and subscribers listen

459
10.4. Architectural Patterns and Message Queues

for messages on that topic. This decouples producers from consumers and enables one-to-many
communication.

Listing 10.55: Code example

1 // Example : Publish - subscribe pattern using RabbitMQ


2

3 public class EventPublisher


4 {
5 private readonly IModel _channel ;
6

7 public EventPublisher ( IConnection connection )


8 {
9 _channel = connection . CreateModel () ;
10 _channel . ExchangeDeclare ( exchange : " order_exchange " , type : ExchangeType .
Fanout ) ;
11 }
12

13 public void PublishEvent ( Order order )


14 {
15 var body = Encoding . UTF8 . GetBytes ( JsonConvert . SerializeObject ( order ) ) ;
16 _channel . BasicPublish ( exchange : " order_exchange " , routingKey : " " ,
basicProperties : null , body : body ) ;
17 }
18 }
19

20 public class EventSubscriber


21 {
22 private readonly IModel _channel ;
23

24 public EventSubscriber ( IConnection connection )


25 {
26 _channel = connection . CreateModel () ;
27 _channel . QueueDeclare ( queue : " order_queue " , durable : true , exclusive :
false , autoDelete : false , arguments : null ) ;
28 _channel . QueueBind ( queue : " order_queue " , exchange : " order_exchange " ,
routingKey : " " ) ;
29 }
30

31 public void SubscribeToEvents ()


32 {
33 var consumer = new EventingBasicConsumer ( _channel ) ;
34 consumer . Received += ( model , ea ) = >
35 {
36 var body = ea . Body . ToArray () ;
37 var message = Encoding . UTF8 . GetString ( body ) ;
38 Console . WriteLine ( " Received event : " + message ) ;
39 };

460
10.4. Architectural Patterns and Message Queues

40 _channel . BasicConsume ( queue : " order_queue " , autoAck : true , consumer :


consumer ) ;
41 }
42 }

How do you design for guaranteed message delivery in message queues?


To guarantee message delivery, use message persistence and acknowledgments. Messages can be
marked as persistent in the message queue to survive crashes. Consumers send acknowledgments
to confirm that the message was successfully processed, ensuring no message is lost.

Listing 10.56: Code example

1 // Example : Guaranteed message delivery with RabbitMQ


2

3 public class PersistentMessageQueue


4 {
5 private readonly IModel _channel ;
6

7 public PersistentMessageQueue ( IConnection connection )


8 {
9 _channel = connection . CreateModel () ;
10 _channel . QueueDeclare ( queue : " persistent_queue " , durable : true , exclusive :
false , autoDelete : false , arguments : null ) ;
11 }
12

13 public void PublishMessage ( Order order )


14 {
15 var body = Encoding . UTF8 . GetBytes ( JsonConvert . SerializeObject ( order ) ) ;
16 var properties = _channel . CreateBasicProperties () ;
17 properties . Persistent = true ; // Make message persistent
18 _channel . BasicPublish ( exchange : " " , routingKey : " persistent_queue " ,
basicProperties : properties , body : body ) ;
19 }
20 }

How does RabbitMQ implement message acknowledgment, and why is it


important?
RabbitMQ uses message acknowledgment to ensure that messages are successfully processed before
being removed from the queue. If a consumer fails to acknowledge a message, it can be re-queued
for processing by another consumer. This guarantees that no message is lost due to consumer
failures.

Listing 10.57: Code example

461
10.4. Architectural Patterns and Message Queues

1 // Example : Acknowledgment in RabbitMQ


2

3 public class MessageConsumer


4 {
5 private readonly IModel _channel ;
6

7 public MessageConsumer ( IConnection connection )


8 {
9 _channel = connection . CreateModel () ;
10 }
11

12 public void ConsumeMessages ()


13 {
14 var consumer = new EventingBasicConsumer ( _channel ) ;
15 consumer . Received += ( model , ea ) = >
16 {
17 var body = ea . Body . ToArray () ;
18 var message = Encoding . UTF8 . GetString ( body ) ;
19 try
20 {
21 // Process the message
22 Console . WriteLine ( " Processed : " + message ) ;
23 _channel . BasicAck ( ea . DeliveryTag , false ) ; // Acknowledge
successful processing
24 }
25 catch ( Exception )
26 {
27 _channel . BasicNack ( ea . DeliveryTag , false , true ) ; // Requeue the
message for retry
28 }
29 };
30 _channel . BasicConsume ( queue : " order_queue " , autoAck : false , consumer :
consumer ) ;
31 }
32 }

What is the difference between a point-to-point messaging pattern and


a publish-subscribe pattern?
In a point-to-point messaging pattern, each message is sent to a single consumer (e.g., a message
queue). In a publish-subscribe pattern, a message is broadcast to multiple subscribers via a topic
or exchange. The former is ideal for task distribution, while the latter is for event dissemination.

Listing 10.58: Code example

1 // Example : Point - to - point messaging with RabbitMQ

462
10.4. Architectural Patterns and Message Queues

3 public class PointToPointMessageQueue


4 {
5 private readonly IModel _channel ;
6

7 public PointToPointMessageQueue ( IConnection connection )


8 {
9 _channel = connection . CreateModel () ;
10 _channel . QueueDeclare ( queue : " task_queue " , durable : true , exclusive : false
, autoDelete : false , arguments : null ) ;
11 }
12

13 public void PublishTask ( Task task )


14 {
15 var body = Encoding . UTF8 . GetBytes ( JsonConvert . SerializeObject ( task ) ) ;
16 _channel . BasicPublish ( exchange : " " , routingKey : " task_queue " ,
basicProperties : null , body : body ) ;
17 }
18 }

How does Kafka ensure message ordering in a partitioned topic?


Kafka ensures message ordering within a partition by always appending messages to the end of the
log in the partition. A single partition is processed by one consumer, ensuring that messages are
consumed in the order they were produced. This guarantees message order for a given key within
a partition.

Listing 10.59: Code example

1 // Example : Kafka producer with partition key to ensure message ordering


2

3 public class KafkaProducer


4 {
5 private readonly IProducer < string , string > _producer ;
6

7 public KafkaProducer ( IProducer < string , string > producer )


8 {
9 _producer = producer ;
10 }
11

12 public async Task ProduceMessageAsync ( string key , string message )


13 {
14 var kafkaMessage = new Message < string , string > { Key = key , Value =
message };
15 await _producer . ProduceAsync ( " my_topic " , kafkaMessage ) ; // Partitioning by
key ensures message ordering within a partition

463
10.4. Architectural Patterns and Message Queues

16 }
17 }

How would you handle poison messages in message queues?


Poison messages are messages that repeatedly fail processing. They can be handled by sending
them to a dead-letter queue after a certain number of retries. This ensures that poison messages
do not block other messages from being processed.
Listing 10.60: Code example

1 // Example : Handling poison messages using RabbitMQ dead - letter queue


2

3 public class DeadLetterQueueHandler


4 {
5 private readonly IModel _channel ;
6

7 public DeadLetterQueueHandler ( IConnection connection )


8 {
9 _channel = connection . CreateModel () ;
10 _channel . QueueDeclare ( queue : " task_queue " , durable : true , exclusive : false
, autoDelete : false , arguments : new Dictionary < string , object >
11 {
12 { "x - dead - letter - exchange " , " dead_letter_exchange " }
13 }) ;
14 _channel . QueueDeclare ( queue : " dead_letter_queue " , durable : true , exclusive
: false , autoDelete : false , arguments : null ) ;
15 }
16

17 public void ConsumeMessages ()


18 {
19 var consumer = new EventingBasicConsumer ( _channel ) ;
20 consumer . Received += ( model , ea ) = >
21 {
22 var body = ea . Body . ToArray () ;
23 var message = Encoding . UTF8 . GetString ( body ) ;
24 try
25 {
26 // Try to process the message
27 ProcessMessage ( message ) ;
28 _channel . BasicAck ( ea . DeliveryTag , false ) ;
29 }
30 catch ( Exception )
31 {
32 _channel . BasicNack ( ea . DeliveryTag , false , false ) ; // Send to dead -
letter queue after failed processing
33 }

464
10.4. Architectural Patterns and Message Queues

34 };
35 _channel . BasicConsume ( queue : " task_queue " , autoAck : false , consumer :
consumer ) ;
36 }
37

38 private void ProcessMessage ( string message )


39 {
40 // Logic to process message
41 throw new Exception ( " Simulating message failure " ) ;
42 }
43 }

How would you implement message filtering in a message broker system?


Message filtering allows consumers to receive only the messages that match certain criteria. This
can be achieved by using message headers or properties. The broker evaluates the filter rules and
routes the message to the appropriate consumer.
Listing 10.61: Code example

1 // Example : Message filtering using RabbitMQ with message headers


2

3 public class FilteredMessagePublisher


4 {
5 private readonly IModel _channel ;
6

7 public FilteredMessagePublisher ( IConnection connection )


8 {
9 _channel = connection . CreateModel () ;
10 _channel . ExchangeDeclare ( exchange : " header_exchange " , type : " headers " ) ;
11 }
12

13 public void PublishMessageWithHeaders ( Order order )


14 {
15 var properties = _channel . CreateBasicProperties () ;
16 properties . Headers = new Dictionary < string , object >
17 {
18 { " order_type " , " urgent " }
19 };
20 var body = Encoding . UTF8 . GetBytes ( JsonConvert . SerializeObject ( order ) ) ;
21 _channel . BasicPublish ( exchange : " header_exchange " , routingKey : " " ,
basicProperties : properties , body : body ) ;
22 }
23 }
24

25 public class FilteredMessageConsumer


26 {

465
10.4. Architectural Patterns and Message Queues

27 private readonly IModel _channel ;


28

29 public FilteredMessageConsumer ( IConnection connection )


30 {
31 _channel = connection . CreateModel () ;
32 _channel . QueueDeclare ( queue : " urgent_queue " , durable : true , exclusive :
false , autoDelete : false , arguments : null ) ;
33 _channel . QueueBind ( queue : " urgent_queue " , exchange : " header_exchange " ,
routingKey : " " , arguments : new Dictionary < string , object >
34 {
35 { "x - match " , " all " } ,
36 { " order_type " , " urgent " }
37 }) ;
38 }
39

40 public void ConsumeUrgentMessages ()


41 {
42 var consumer = new EventingBasicConsumer ( _channel ) ;
43 consumer . Received += ( model , ea ) = >
44 {
45 var body = ea . Body . ToArray () ;
46 var message = Encoding . UTF8 . GetString ( body ) ;
47 Console . WriteLine ( " Received urgent order : " + message ) ;
48 };
49 _channel . BasicConsume ( queue : " urgent_queue " , autoAck : true , consumer :
consumer ) ;
50 }
51 }

How do you ensure idempotency when processing messages from a mes-


sage queue?
Idempotency ensures that processing the same message multiple times results in the same outcome.
This can be achieved by keeping track of processed messages (using a unique message ID) and
ignoring duplicate processing attempts.

Listing 10.62: Code example

1 // Example : Ensuring idempotency by tracking processed message IDs


2

3 public class IdempotentMessageConsumer


4 {
5 private readonly HashSet < string > _processedMessageIds = new HashSet < string >() ;
6

7 public void ProcessMessage ( string messageId , string messageBody )


8 {

466
10.4. Architectural Patterns and Message Queues

9 if ( _processedMessageIds . Contains ( messageId ) )


10 {
11 Console . WriteLine ( " Duplicate message detected , ignoring ... " ) ;
12 return ;
13 }
14

15 _processedMessageIds . Add ( messageId ) ;


16 // Process the message
17 Console . WriteLine ( " Processed message : " + messageBody ) ;
18 }
19 }

How do you handle message prioritization in message queues?


Message prioritization allows high-priority messages to be processed before low-priority ones. This
can be implemented by assigning priority levels to messages, and the message queue processes
messages based on their priority.

Listing 10.63: Code example

1 // Example : Message prioritization with RabbitMQ


2

3 public class PriorityMessagePublisher


4 {
5 private readonly IModel _channel ;
6

7 public PriorityMessagePublisher ( IConnection connection )


8 {
9 _channel = connection . CreateModel () ;
10 _channel . QueueDeclare ( queue : " priority_queue " , durable : true , exclusive :
false , autoDelete : false , arguments : new Dictionary < string , object >
11 {
12 { "x - max - priority " , 10 }
13 }) ;
14 }
15

16 public void PublishMessage ( Order order , int priority )


17 {
18 var properties = _channel . CreateBasicProperties () ;
19 properties . Priority = ( byte ) priority ; // Set message priority
20 var body = Encoding . UTF8 . GetBytes ( JsonConvert . SerializeObject ( order ) ) ;
21 _channel . BasicPublish ( exchange : " " , routingKey : " priority_queue " ,
basicProperties : properties , body : body ) ;
22 }
23 }

467
10.5. System Architecture: Layered Modular Design with
Data, Service, Core, UI, and Common Code Layers

10.5 System Architecture: Layered Modular Design with


Data, Service, Core, UI, and Common Code Layers
What are the benefits of a layered architecture in system design?
A layered architecture separates concerns, improving maintainability, scalability, and testability.
Each layer has a distinct responsibility:
• Data Layer: Handles database operations and data access using repositories.
• Service Layer: Contains business logic and orchestrates interactions between the data layer
and the core or UI layers.
• Core Layer: Defines shared models, logic, and interfaces used across modules.
• UI Layer: Manages all user interface components and user interactions.
• Common Code Layer: Provides utilities, shared helpers, and cross-cutting concerns.

How would you implement the repository pattern in the data layer?
The repository pattern abstracts database interactions, encapsulating CRUD operations. This
pattern improves testability by decoupling the data layer from the business logic.

Listing 10.64: Code example

1 public interface IRepository <T >


2 {
3 Task < IEnumerable <T > > GetAllAsync () ;
4 Task <T > GetByIdAsync ( int id ) ;
5 Task AddAsync ( T entity ) ;
6 Task UpdateAsync ( T entity ) ;
7 Task DeleteAsync ( int id ) ;
8 }
9

10 public class ProductRepository : IRepository < Product >


11 {
12 private readonly AppDbContext _context ;
13

14 public ProductRepository ( AppDbContext context )


15 {
16 _context = context ;
17 }
18

19 public async Task < IEnumerable < Product > > GetAllAsync () = > await _context .
Products . ToListAsync () ;
20 public async Task < Product > GetByIdAsync ( int id ) = > await _context . Products .
FindAsync ( id ) ;

468
10.5. System Architecture: Layered Modular Design with
Data, Service, Core, UI, and Common Code Layers

21 public async Task AddAsync ( Product entity ) { await _context . Products . AddAsync (
entity ) ; await _context . SaveChangesAsync () ; }
22 public async Task UpdateAsync ( Product entity ) { _context . Products . Update (
entity ) ; await _context . SaveChangesAsync () ; }
23 public async Task DeleteAsync ( int id ) { var product = await GetByIdAsync ( id ) ;
_context . Products . Remove ( product ) ; await _context . SaveChangesAsync () ; }
24 }

How do you implement a service layer to enforce business rules?


The service layer acts as the intermediary between the UI and the data layer. It applies business
rules, validations, and orchestration logic.

Listing 10.65: Code example

1 public interface IProductService


2 {
3 Task < IEnumerable < Product > > GetAvailableProductsAsync () ;
4 Task < Product > AddProductAsync ( Product product ) ;
5 }
6

7 public class ProductService : IProductService


8 {
9 private readonly IRepository < Product > _repository ;
10

11 public ProductService ( IRepository < Product > repository )


12 {
13 _repository = repository ;
14 }
15

16 public async Task < IEnumerable < Product > > GetAvailableProductsAsync ()
17 {
18 return ( await _repository . GetAllAsync () ) . Where ( p = > p . IsAvailable ) ;
19 }
20

21 public async Task < Product > AddProductAsync ( Product product )


22 {
23 if ( string . IsNullOrEmpty ( product . Name ) )
24 throw new ArgumentException ( " Product name is required . " ) ;
25

26 await _repository . AddAsync ( product ) ;


27 return product ;
28 }
29 }

469
10.5. System Architecture: Layered Modular Design with
Data, Service, Core, UI, and Common Code Layers

How do you structure the core layer to maximize reusability across mod-
ules?
The core layer contains:
• Shared Models: Represent domain entities and are used across layers (e.g., ‘Product‘,
‘Order‘).
• Interfaces: Define contracts for repository and service layers.
• Validators: Ensure consistent validation logic.
Listing 10.66: Code example

1 // Core Model
2 public class Product
3 {
4 public int Id { get ; set ; }
5 public string Name { get ; set ; }
6 public bool IsAvailable { get ; set ; }
7 }
8

9 // Validator in Core Layer


10 public static class ProductValidator
11 {
12 public static void Validate ( Product product )
13 {
14 if ( string . IsNullOrEmpty ( product . Name ) )
15 throw new ArgumentException ( " Name cannot be null or empty . " ) ;
16 }
17 }

How can you design the UI layer to interact with the service layer effec-
tively?
The UI layer should:
• Use dependency injection to access services.
• Invoke service layer methods to retrieve or manipulate data.
• Map domain models to UI-specific view models if needed.
Listing 10.67: Code example

1 public class ProductController : Controller


2 {
3 private readonly IProductService _productService ;
4

5 public ProductController ( IProductService productService )


6 {
7 _productService = productService ;

470
10.5. System Architecture: Layered Modular Design with
Data, Service, Core, UI, and Common Code Layers

8 }
9

10 [ HttpGet ]
11 public async Task < IActionResult > Index ()
12 {
13 var products = await _productService . GetAvailableProductsAsync () ;
14 return View ( products ) ;
15 }
16 }

How do you handle cross-cutting concerns in the common code layer?


Cross-cutting concerns like logging, caching, and exception handling are implemented in the com-
mon code layer. These are often achieved through middleware or shared services.

Listing 10.68: Code example

1 public static class Logger


2 {
3 public static void LogInfo ( string message )
4 {
5 Console . WriteLine ( $ " INFO : { message } " ) ;
6 }
7 }
8

9 public class ExceptionMiddleware


10 {
11 private readonly RequestDelegate _next ;
12

13 public ExceptionMiddleware ( RequestDelegate next )


14 {
15 _next = next ;
16 }
17

18 public async Task Invoke ( HttpContext context )


19 {
20 try
21 {
22 await _next ( context ) ;
23 }
24 catch ( Exception ex )
25 {
26 Logger . LogInfo ( $ " Exception : { ex . Message } " ) ;
27 context . Response . StatusCode = 500;
28 await context . Response . WriteAsync ( " An error occurred . " ) ;
29 }
30 }

471
10.5. System Architecture: Layered Modular Design with
Data, Service, Core, UI, and Common Code Layers

31 }

How do you organize modules in a multi-module architecture?


Each module should encapsulate:
• Data Layer: Module-specific repositories.
• Service Layer: Module-specific business logic.
• Core Layer: Shared models/interfaces specific to the module.
• UI Layer: Module-specific views and controllers.

Folder Structure Example:

Listing 10.69: Structure

1 Modules /
2 Products /
3 Data /
4 ProductRepository . cs
5 Services /
6 ProductService . cs
7 Core /
8 Product . cs
9 ProductValidator . cs
10 UI /
11 ProductController . cs
12 Orders /
13 Data /
14 OrderRepository . cs
15 Services /
16 OrderService . cs
17 Core /
18 Order . cs
19 OrderValidator . cs
20 UI /
21 OrderController . cs
22 Common /
23 Logging /
24 Logger . cs
25 Middleware /
26 ExceptionMiddleware . cs

How do you ensure modularity while sharing common logic?


• Place shared logic in the **Common Code Layer**.
• Use dependency injection to inject shared services into modules.

472
10.5. System Architecture: Layered Modular Design with
Data, Service, Core, UI, and Common Code Layers

• Avoid tight coupling by defining interfaces in the core layer.

How can you decouple the UI layer from the service layer in a modular
architecture?
• Use view models to transform data from the service layer.
• Use mediator patterns like CQRS for indirect communication.

How do you implement module-specific configurations?


Use configuration files (e.g., ‘appsettings.json‘) with sections for each module and inject ‘IOp-
tions<T>‘ into the module’s services.

Listing 10.70: Code example

1 " Products " : {


2 " MaxItems " : 100
3 }
4

5 public class ProductConfig


6 {
7 public int MaxItems { get ; set ; }
8 }

What are the advantages of using a modular architecture for large-scale


systems?
A modular architecture:
• Enables separation of concerns, making systems easier to maintain.
• Promotes reusability by isolating features into independent modules.
• Improves scalability by allowing independent development and deployment of modules.
• Enhances testability by providing clear boundaries for unit and integration tests.

How do you handle shared dependencies across modules in a modular


architecture?
Shared dependencies, like logging or caching, are placed in a **Common Code Layer** and injected
into individual modules using dependency injection.

473
10.5. System Architecture: Layered Modular Design with
Data, Service, Core, UI, and Common Code Layers

How do you design a repository to support multiple databases?


Abstract the repository interface and implement separate classes for each database type. Use a
factory or DI to resolve the correct implementation at runtime.
Listing 10.71: Code example

1 public interface IProductRepository


2 {
3 Task < IEnumerable < Product > > GetAllAsync () ;
4 }
5

6 public class SqlProductRepository : IProductRepository


7 {
8 public Task < IEnumerable < Product > > GetAllAsync () = > // SQL logic
9 }
10

11 public class MongoProductRepository : IProductRepository


12 {
13 public Task < IEnumerable < Product > > GetAllAsync () = > // MongoDB logic
14 }

How would you implement transactions across multiple repositories?


Use a Unit of Work (UoW) pattern to manage transactions across repositories.
Listing 10.72: Code example

1 public interface IUnitOfWork : IDisposable


2 {
3 IProductRepository ProductRepository { get ; }
4 Task CommitAsync () ;
5 }
6

7 public class UnitOfWork : IUnitOfWork


8 {
9 private readonly AppDbContext _context ;
10

11 public UnitOfWork ( AppDbContext context )


12 {
13 _context = context ;
14 ProductRepository = new ProductRepository ( _context ) ;
15 }
16

17 public IProductRepository ProductRepository { get ; }


18 public async Task CommitAsync () = > await _context . SaveChangesAsync () ;
19 public void Dispose () = > _context . Dispose () ;
20 }

474
10.5. System Architecture: Layered Modular Design with
Data, Service, Core, UI, and Common Code Layers

How do you ensure the service layer adheres to the Single Responsibility
Principle (SRP)?
• Limit each service class to a specific use case or business capability.
• Delegate cross-cutting concerns (e.g., logging) to helpers or middleware.
• Avoid combining unrelated logic in the same service.

How do you handle domain events in a layered architecture?


Use a mediator or event bus to decouple domain events from their handlers. Publish events from
the service layer and handle them asynchronously.

Listing 10.73: Code example

1 public class ProductCreatedEvent


2 {
3 public int ProductId { get ; set ; }
4 }
5

6 public class EventBus


7 {
8 private readonly List < Action < ProductCreatedEvent > > _subscribers = new () ;
9

10 public void Publish ( ProductCreatedEvent evt )


11 {
12 foreach ( var subscriber in _subscribers )
13 {
14 subscriber ( evt ) ;
15 }
16 }
17

18 public void Subscribe ( Action < ProductCreatedEvent > handler )


19 {
20 _subscribers . Add ( handler ) ;
21 }
22 }

How do you enforce consistency across layers in modular systems?


• Define interfaces in the core layer to ensure consistency between layers.
• Use shared DTOs and validators to standardize data handling.
• Apply architectural principles like Domain-Driven Design (DDD).

475
10.5. System Architecture: Layered Modular Design with
Data, Service, Core, UI, and Common Code Layers

How would you handle data caching at the service layer?


Implement a caching strategy using in-memory caches (e.g., ‘IMemoryCache‘) or distributed caches
(e.g., Redis). Cache results of frequently accessed service methods.

How do you manage versioning in a modular architecture?


• Use API versioning for UI interactions.
• Maintain backward-compatible changes in shared services.
• Use dependency injection to manage different service versions.

How can you test the layers in isolation?


• Unit Tests: Mock dependencies to test service or repository logic independently.
• Integration Tests: Use in-memory databases to test data layer behavior.
• End-to-End Tests: Simulate user scenarios to verify interactions across layers.

How do you design the UI layer to support multiple clients (e.g., web,
mobile)?
Abstract UI logic into reusable services or components. Implement APIs for interaction, and share
core libraries between client platforms.

How do you organize validation logic for shared models?


Place validators in the core layer or use decorators like ‘[ValidationAttribute]‘ for shared models.

How can you enforce modular boundaries in a multi-module architecture?


• Use namespaces and folder structures to segregate modules.
• Restrict dependencies using access modifiers and assembly-level configurations.

What strategies can you use for inter-module communication?


• Use an event-driven architecture (e.g., mediator pattern).
• Expose module-specific services through well-defined APIs.

How do you ensure the common code layer doesn’t become a bottleneck?
• Regularly refactor the common layer to remove unused utilities.
• Avoid coupling module-specific logic into the common layer.

476
10.5. System Architecture: Layered Modular Design with
Data, Service, Core, UI, and Common Code Layers

How can you make the repository layer asynchronous?


Use asynchronous methods for database operations with ‘async‘/‘await‘.

How would you handle different service implementations for each mod-
ule?
Define service contracts in the core layer and inject module-specific implementations at runtime.

What is the difference between DTOs and domain models, and where do
you use each?
• DTOs: Used for transferring data between layers or modules. Focused on serialization.
• Domain Models: Represent business entities. Used in the core layer for business logic.

How can you optimize database calls in the data layer?


• Use caching for repeated queries.
• Use ‘Include‘ for eager loading.
• Apply query optimizations like pagination.

How do you handle concurrency in modular systems?


• Use optimistic concurrency (e.g., row versioning).
• Use distributed locks for cross-module operations.

How do you ensure cross-cutting concerns like logging are modular?


• Use middleware for request/response logging.
• Share logging utilities in the common layer.

How do you secure communication between modules in a distributed


system?
• Use encryption (e.g., HTTPS or message-level security).
• Implement authentication and authorization at API boundaries.

477
10.5. System Architecture: Layered Modular Design with
Data, Service, Core, UI, and Common Code Layers

How do you implement modular exception handling?


• Use a global exception handler for module-specific APIs.
• Log module-level exceptions to centralized monitoring tools.

How do you ensure scalability in a multi-module architecture?


• Scale modules independently.
• Use message queues or brokers to handle inter-module communication.

How do you implement audit logging in a layered architecture?


Capture key events in the service layer and log them to a centralized audit system.

How do you ensure database migrations are handled across modules?


Use migration tools (e.g., EF Core migrations) and segregate migrations by module.

How do you avoid circular dependencies in modular designs?


• Use dependency injection.
• Define shared contracts in the core layer.

How do you organize unit tests in a multi-module architecture?


• Maintain separate test projects per module.
• Use mock services to isolate layers.

478
11 | Concurrency and Multithread-
ing

Concurrency and multithreading are essential for developing responsive and high-performance ap-
plications in .NET. These concepts allow programs to execute multiple operations in parallel,
making efficient use of system resources, especially in scenarios involving I/O-bound or CPU-
intensive tasks.

Understanding how to manage threads, avoid race conditions, and implement synchronization
mechanisms is critical in building stable and scalable systems. The .NET platform provides pow-
erful tools such as the Task Parallel Library (TPL), async/await, and Concurrent collections to
simplify concurrent programming while maintaining safety and readability.

In job interviews, candidates are frequently evaluated on their ability to reason about parallel
execution, identify concurrency pitfalls, and apply best practices to avoid common issues like dead-
locks or thread contention. Proficiency in this area signals to employers that you can build modern
applications that perform well under load and remain responsive under real-world conditions.

11.1 Concurrency and Multithreading Basics


What is the difference between concurrency and parallelism in .NET?
Concurrency refers to the ability of a program to deal with multiple tasks by managing them in
overlapping periods of time, whereas parallelism involves executing multiple tasks simultaneously.
In .NET, concurrency is managed through constructs like ‘async/await‘, and parallelism can be
achieved using multithreading or the Task Parallel Library (TPL).

Listing 11.1: Code example

1 // Example : Concurrency using async / await in . NET


2 public async Task < int > FetchDataAsync ()
3 {
4 // Concurrency : tasks run asynchronously but not necessarily in parallel

479
11.1. Concurrency and Multithreading Basics

5 var task1 = GetDataFromApiAsync () ;


6 var task2 = GetDataFromDatabaseAsync () ;
7

8 await Task . WhenAll ( task1 , task2 ) ; // Tasks are concurrent


9 return task1 . Result + task2 . Result ;
10 }

How does multithreading work in .NET, and what are the key concepts
behind it?
Multithreading in .NET allows an application to execute multiple threads simultaneously. Each
thread runs independently, and threads can be created using the ‘Thread‘ class or through the
‘Task‘ class for higher-level concurrency management.

Listing 11.2: Code example

1 // Example : Basic multithreading using the Thread class


2 public void StartThread ()
3 {
4 Thread thread = new Thread (() = >
5 {
6 Console . WriteLine ( " Thread execution started . " ) ;
7 }) ;
8 thread . Start () ; // Start the new thread
9 thread . Join () ; // Wait for the thread to complete
10 }

What is the ‘Task‘ class, and how does it simplify multithreading in


.NET?
The ‘Task‘ class in .NET represents an asynchronous operation and simplifies multithreading
by managing thread pools and providing higher-level abstractions for running tasks concurrently
without directly managing threads. The ‘Task‘ class is used extensively with the ‘async/await‘
pattern.

Listing 11.3: Code example

1 // Example : Using the Task class for asynchronous operations


2 public async Task < int > CalculateSumAsync ( int a , int b )
3 {
4 return await Task . Run (() = > a + b ) ; // Task represents a background operation
5 }

480
11.1. Concurrency and Multithreading Basics

What are thread safety issues, and how can they be prevented in .NET
applications?
Thread safety issues occur when multiple threads access shared resources without proper synchro-
nization, leading to race conditions or corrupted state. Thread safety can be ensured by using
synchronization mechanisms like locks (‘lock‘ statement) or by using thread-safe collections.

Listing 11.4: Code example

1 // Example : Ensuring thread safety with a lock


2 private static readonly object _lockObject = new object () ;
3 private int _counter = 0;
4

5 public void IncrementCounter ()


6 {
7 lock ( _lockObject ) // Ensure only one thread can access the critical section
8 {
9 _counter ++;
10 }
11 }

What is the difference between the ‘lock‘ statement and ‘Monitor‘ class
in .NET?
The ‘lock‘ statement in C# is a shorthand for the ‘Monitor‘ class. Both are used for thread syn-
chronization, but ‘Monitor‘ provides more control, such as trying to enter a lock without blocking
(‘Monitor.TryEnter‘) and signaling with ‘Pulse‘ or ‘PulseAll‘.

Listing 11.5: Code example

1 // Example : Using Monitor instead of lock


2 private static readonly object _syncObject = new object () ;
3

4 public void UseMonitor ()


5 {
6 if ( Monitor . TryEnter ( _syncObject ) )
7 {
8 try
9 {
10 // Critical section
11 }
12 finally
13 {
14 Monitor . Exit ( _syncObject ) ; // Release the lock
15 }
16 }

481
11.1. Concurrency and Multithreading Basics

17 }

What is a deadlock, and how can it be avoided in .NET multithreading?


A deadlock occurs when two or more threads are waiting indefinitely for resources held by each
other, causing the program to freeze. Deadlocks can be avoided by ensuring a consistent locking
order, using timeouts (‘Monitor.TryEnter‘), or avoiding nested locks.
Listing 11.6: Code example

1 // Example : Avoiding deadlock with proper lock ordering


2 private readonly object _lock1 = new object () ;
3 private readonly object _lock2 = new object () ;
4

5 public void MethodA ()


6 {
7 lock ( _lock1 )
8 {
9 lock ( _lock2 )
10 {
11 // Do work
12 }
13 }
14 }

What is the ‘ThreadPool‘, and how does it manage threads in .NET?


The ‘ThreadPool‘ in .NET manages a pool of worker threads that can be reused for executing
short-lived tasks, reducing the overhead of creating and destroying threads. Tasks are queued to
the ‘ThreadPool‘, and available threads execute them.
Listing 11.7: Code example

1 // Example : Using ThreadPool to queue a work item


2 ThreadPool . QueueUserWorkItem ( state = >
3 {
4 Console . WriteLine ( " Work item executed by ThreadPool . " ) ;
5 }) ;

What is the ‘async‘ and ‘await‘ pattern in .NET, and how does it help
with concurrency?
The ‘async‘ and ‘await‘ pattern in .NET allows asynchronous programming by marking methods as
‘async‘ and awaiting asynchronous tasks. This enables non-blocking I/O operations and improves
concurrency without the need to explicitly manage threads.

482
11.1. Concurrency and Multithreading Basics

Listing 11.8: Code example

1 // Example : Using async and await to improve concurrency


2 public async Task < int > FetchDataAsync ()
3 {
4 var apiResult = await GetDataFromApiAsync () ; // Non - blocking
5 var dbResult = await GetDataFromDatabaseAsync () ; // Non - blocking
6

7 return apiResult + dbResult ; // The method returns once all async operations
are complete
8 }

How to Know if an Await Task Already Finished and Handle Exceptions


in Multiple Await Tasks?
You can determine if an ‘await‘ task has already finished by checking its ‘IsCompleted‘ property.
To handle exceptions in multiple await tasks, you can use a ‘try-catch‘ block or aggregate multiple
tasks using ‘Task.WhenAll‘ and handle exceptions for each task individually.

Listing 11.9: Code example

1 // Example : Checking if a task has completed


2 var task = SomeAsyncOperation () ;
3

4 if ( task . IsCompleted )
5 {
6 Console . WriteLine ( " Task already finished " ) ;
7 }
8 else
9 {
10 Console . WriteLine ( " Task still running " ) ;
11 await task ; // Wait for the task to complete
12 }
13

14 // Handling exceptions in multiple tasks


15 var task1 = SomeAsyncOperation () ;
16 var task2 = AnotherAsyncOperation () ;
17

18 try
19 {
20 // Wait for all tasks to complete
21 await Task . WhenAll ( task1 , task2 ) ;
22 }
23 catch ( Exception ex )
24 {
25 Console . WriteLine ( $ " An exception occurred : { ex . Message } " ) ;
26 }

483
11.1. Concurrency and Multithreading Basics

27

28 // Handling individual task exceptions


29 if ( task1 . IsFaulted )
30 {
31 Console . WriteLine ( $ " Task 1 failed : { task1 . Exception ?. Message } " ) ;
32 }
33

34 if ( task2 . IsFaulted )
35 {
36 Console . WriteLine ( $ " Task 2 failed : { task2 . Exception ?. Message } " ) ;
37 }

What are the advantages and disadvantages of using ‘Task.Run‘ for asyn-
chronous code in .NET?
‘Task.Run‘ is useful for offloading work to a background thread, especially CPU-bound tasks. It
simplifies multithreading but is not ideal for I/O-bound tasks or when fine control over threads is
required. Using ‘Task.Run‘ for I/O-bound operations can result in unnecessary thread pool usage.

Listing 11.10: Code example

1 // Example : Using Task . Run for a CPU - bound operation


2 public async Task < int > PerformComputationAsync ( int a , int b )
3 {
4 return await Task . Run (() = > a + b ) ; // Offload work to a background thread
5 }

What is a race condition, and how can it be avoided in multithreaded


code?
A race condition occurs when multiple threads access shared data concurrently, and the outcome
depends on the timing of the threads. Race conditions can be avoided by using synchronization
mechanisms like locks or thread-safe collections.

Listing 11.11: Code example

1 // Example : Avoiding race conditions using a lock


2 private readonly object _syncLock = new object () ;
3 private int _sharedData ;
4

5 public void UpdateData ()


6 {
7 lock ( _syncLock ) // Synchronize access to shared data
8 {
9 _sharedData ++;

484
11.1. Concurrency and Multithreading Basics

10 }
11 }

What are thread-local variables, and how are they used in .NET?
Thread-local variables store data that is specific to each thread. In .NET, the ‘ThreadLocal<T>‘
class is used to create variables that are local to a thread, preventing access from other threads
and avoiding synchronization issues.

Listing 11.12: Code example

1 // Example : Using ThreadLocal to create thread - specific data


2 private static ThreadLocal < int > _threadLocalData = new ThreadLocal < int >(() = >
Thread . CurrentThread . ManagedThreadId ) ;
3

4 public void PrintThreadLocalData ()


5 {
6 Console . WriteLine ( $ " Thread ID : { _threadLocalData . Value } " ) ;
7 }

What is a real-world use case for ‘ThreadLocal<T>‘ in .NET?


A practical application of ‘ThreadLocal<T>‘ in .NET is managing **thread-specific context in-
formation** in a multithreaded logging system. By assigning a unique ‘RequestID‘ to each thread
processing web requests, you can ensure that logs are traceable and thread-safe without the need
for locks.
Scenario: Thread-Specific Request IDs for Logging In a web server handling multiple
concurrent requests, each request is processed by a different thread. Using ‘ThreadLocal<T>‘,
you can assign a unique ‘RequestID‘ to each thread, which is then used to log context-specific
messages.

Listing 11.13: Output

1 [2024 -11 -21 12:00:00] Thread 1 ( RequestID : REQ -12345678) - Processing step 1
2 [2024 -11 -21 12:00:00] Thread 1 ( RequestID : REQ -12345678) - Processing step 2
3 [2024 -11 -21 12:00:00] Thread 2 ( RequestID : REQ -87654321) - Processing step 1
4 [2024 -11 -21 12:00:00] Thread 2 ( RequestID : REQ -87654321) - Processing step 2

Benefits of Using ‘ThreadLocal<T>‘:


• Thread Isolation: Each thread maintains its own ‘RequestID‘, preventing interference with
other threads.
• Traceability: Logs can be correlated to the specific request and thread that generated them.
• Performance: Eliminates the need for locks or shared data structures, reducing contention.

485
11.1. Concurrency and Multithreading Basics

Listing 11.14: Code example

1 using System ;
2 using System . Threading ;
3

4 class Program
5 {
6 // Thread - local storage for RequestID
7 private static ThreadLocal < string > threadRequestID = new ThreadLocal < string
>(() = > $ " REQ -{ Guid . NewGuid () } " ) ;
8

9 static void Main ()


10 {
11 // Simulate handling multiple requests in parallel
12 Thread thread1 = new Thread (() = > ProcessRequest ( " Thread 1 " ) ) ;
13 Thread thread2 = new Thread (() = > ProcessRequest ( " Thread 2 " ) ) ;
14

15 thread1 . Start () ;
16 thread2 . Start () ;
17

18 thread1 . Join () ;
19 thread2 . Join () ;
20 }
21

22 static void ProcessRequest ( string threadName )


23 {
24 // Each thread gets its own RequestID
25 string requestID = threadRequestID . Value ;
26

27 for ( int i = 0; i < 3; i ++)


28 {
29 Log ( threadName , requestID , $ " Processing step { i + 1} " ) ;
30 }
31 }
32

33 static void Log ( string threadName , string requestID , string message )


34 {
35 Console . WriteLine ( $ " [{ DateTime . Now }] { threadName } ( RequestID : { requestID })
- { message } " ) ;
36 }
37 }

486
11.1. Concurrency and Multithreading Basics

How does ‘Task.WhenAll‘ work, and when should you use it in concurrent
programming?
‘Task.WhenAll‘ is used to wait for multiple tasks to complete. It is useful when you need to
perform multiple asynchronous operations concurrently and aggregate their results. It returns a
task that completes when all the provided tasks are finished.

Listing 11.15: Code example

1 // Example : Using Task . WhenAll to run multiple tasks concurrently


2 public async Task RunMultipleTasksAsync ()
3 {
4 var task1 = GetDataFromApiAsync () ;
5 var task2 = GetDataFromDatabaseAsync () ;
6

7 await Task . WhenAll ( task1 , task2 ) ; // Wait for both tasks to complete
8 }

What is the ‘CancellationToken‘ in .NET, and how does it help in man-


aging multithreaded tasks?
A ‘CancellationToken‘ allows you to signal that a task should be canceled. It is used to gracefully
terminate long-running tasks or threads when no longer needed, preventing unnecessary resource
consumption.

Listing 11.16: Code example

1 // Example : Using CancellationToken to cancel a task


2 public async Task RunTaskWithCancellationAsync ( CancellationToken token )
3 {
4 for ( int i = 0; i < 100; i ++)
5 {
6 token . ThrowIfCancellationRequested () ; // Check for cancellation
7 await Task . Delay (100) ; // Simulate work
8 }
9 }

What are async locks, and how can you implement them using
‘SemaphoreSlim‘ in .NET?
Async locks allow asynchronous methods to safely access shared resources. ‘SemaphoreSlim‘ is
commonly used for async locking, allowing only a specified number of threads to access a resource
at the same time.

487
11.1. Concurrency and Multithreading Basics

Listing 11.17: Code example

1 // Example : Implementing async lock using SemaphoreSlim


2 private static SemaphoreSlim _semaphore = new SemaphoreSlim (1 , 1) ;
3

4 public async Task PerformAsyncTaskWithLock ()


5 {
6 await _semaphore . WaitAsync () ; // Acquire the lock
7 try
8 {
9 // Critical section
10 }
11 finally
12 {
13 _semaphore . Release () ; // Release the lock
14 }
15 }

What is the purpose of ‘ConcurrentDictionary‘ in .NET, and how is it


used?
‘ConcurrentDictionary‘ is a thread-safe dictionary that allows multiple threads to read and write
without locking. It is designed for use in highly concurrent environments where data integrity
must be maintained without using explicit locks.

Listing 11.18: Code example

1 // Example : Using ConcurrentDictionary for thread - safe operations


2 private static ConcurrentDictionary < int , string > _concurrentDict = new
ConcurrentDictionary < int , string >() ;
3

4 public void AddToDictionary ( int key , string value )


5 {
6 _concurrentDict . TryAdd ( key , value ) ;
7 }

How does ‘Parallel.ForEach‘ differ from a standard ‘foreach‘ loop in


.NET?
‘Parallel.ForEach‘ processes collections in parallel, distributing the workload in multiple threads,
whereas a standard ‘foreach‘ loop processes items sequentially. ‘Parallel.ForEach‘ improves perfor-
mance for CPU-bound operations by utilizing multiple cores.

Listing 11.19: Code example

1 // Example : Using Parallel . ForEach for parallel processing

488
11.2. Thread Management

2 Parallel . ForEach ( Enumerable . Range (1 , 100) , number = >


3 {
4 Console . WriteLine ( $ " Processing number { number } on thread { Thread . CurrentThread
. ManagedThreadId } " ) ;
5 }) ;

What is the purpose of ‘async void‘, and why should it generally be


avoided in .NET?
‘async void‘ is used for event handlers in .NET but should generally be avoided because it does
not return a ‘Task‘, making it difficult to handle exceptions and manage control flow. Use ‘async
Task‘ for methods that are expected to return asynchronously.
Listing 11.20: Code example

1 // Example : Avoiding async void by using async Task


2 public async Task < int > GetDataAsync ()
3 {
4 await Task . Delay (1000) ;
5 return 42;
6 }

How does ‘Thread.Sleep‘ differ from ‘Task.Delay‘, and when should each
be used?
‘Thread.Sleep‘ blocks the current thread for a specified amount of time, preventing other tasks
from running on that thread. ‘Task.Delay‘, on the other hand, asynchronously pauses execution
without blocking the thread, allowing other tasks to run.
Listing 11.21: Code example

1 // Example : Using Task . Delay for non - blocking delay


2 public async Task PerformTaskWithDelay ()
3 {
4 await Task . Delay (1000) ; // Non - blocking delay
5 Console . WriteLine ( " Task completed . " ) ;
6 }

11.2 Thread Management


What is a thread in .NET, and how does the CLR manage threads?
A thread in .NET is the smallest unit of execution managed by the CLR. The CLR manages threads
via a thread pool and can create, execute, and terminate threads as needed. The .NET Framework

489
11.2. Thread Management

provides the ‘Thread‘ class to manually create threads, but modern development usually relies on
higher-level constructs like ‘Task‘.

Listing 11.22: Code example

1 // Example : Creating a thread using the Thread class


2 Thread thread = new Thread (() = >
3 {
4 Console . WriteLine ( " Thread execution started . " ) ;
5 }) ;
6 thread . Start () ;
7 thread . Join () ; // Wait for the thread to complete

What is the difference between a foreground and a background thread in


.NET?
Foreground threads keep the application alive until they finish execution, while background threads
are terminated when all foreground threads finish. In .NET, the ‘IsBackground‘ property controls
whether a thread is foreground or background.

Listing 11.23: Code example

1 // Example : Creating a background thread


2 Thread thread = new Thread (() = >
3 {
4 Console . WriteLine ( " Background thread started . " ) ;
5 }) ;
6 thread . IsBackground = true ;
7 thread . Start () ;

How do you start and stop threads in .NET?


Threads in .NET are started using the ‘Thread.Start()‘ method. However, there is no direct
method to stop a thread. Instead, you should implement a cooperative cancellation mechanism
using flags or a ‘CancellationToken‘.

Listing 11.24: Code example

1 // Example : Starting and stopping a thread using a flag for cancellation


2 private bool _isRunning = true ;
3

4 public void StartThread ()


5 {
6 Thread thread = new Thread (() = >
7 {
8 while ( _isRunning )

490
11.2. Thread Management

9 {
10 Console . WriteLine ( " Thread is running ... " ) ;
11 Thread . Sleep (1000) ;
12 }
13 }) ;
14 thread . Start () ;
15 }
16

17 public void StopThread ()


18 {
19 _isRunning = false ;
20 }

What are thread priorities in .NET, and how are they used?
Thread priorities in .NET are used to influence the order in which the operating system schedules
threads for execution. The ‘ThreadPriority‘ enum has values such as ‘Lowest‘, ‘BelowNormal‘,
‘Normal‘, ‘AboveNormal‘, and ‘Highest‘. It is important to note that thread priorities do not
guarantee exact execution order.

Listing 11.25: Code example

1 // Example : Setting thread priority


2 Thread thread = new Thread (() = >
3 {
4 Console . WriteLine ( " Thread with high priority started . " ) ;
5 }) ;
6 thread . Priority = ThreadPriority . Highest ;
7 thread . Start () ;

How do you prevent a race condition when using multiple threads?


A race condition occurs when two or more threads access shared data at the same time, leading
to unpredictable results. You can prevent race conditions using synchronization mechanisms like
the ‘lock‘ statement, ‘Monitor‘, or ‘Mutex‘.

Listing 11.26: Code example

1 // Example : Using a lock to prevent race conditions


2 private static readonly object _syncLock = new object () ;
3 private int _sharedResource ;
4

5 public void UpdateSharedResource ()


6 {
7 lock ( _syncLock )
8 {

491
11.2. Thread Management

9 _sharedResource ++;
10 }
11 }

What is the difference between the ‘lock‘ statement and ‘Mutex‘ in .NET?
The ‘lock‘ statement is a shorthand for ‘Monitor.Enter/Exit‘ and works only within a single process.
A ‘Mutex‘, on the other hand, can be used to synchronize access across multiple processes, not
just threads within the same application.
Listing 11.27: Code example

1 // Example : Using Mutex for cross - process synchronization


2 Mutex mutex = new Mutex ( false , " GlobalMutex " ) ;
3 mutex . WaitOne () ; // Wait for mutex to be available
4

5 try
6 {
7 // Critical section
8 }
9 finally
10 {
11 mutex . ReleaseMutex () ; // Release the mutex
12 }

How do you implement thread-safe collections in .NET?


Thread-safe collections in .NET, such as ‘ConcurrentDictionary‘, ‘ConcurrentQueue‘, and ‘Con-
currentBag‘, allow multiple threads to safely add and remove elements without needing explicit
locks or synchronization mechanisms.
Listing 11.28: Code example

1 // Example : Using ConcurrentDictionary for thread - safe operations


2 ConcurrentDictionary < int , string > dict = new ConcurrentDictionary < int , string >() ;
3 dict . TryAdd (1 , " Value1 " ) ;
4

5 Parallel . ForEach ( Enumerable . Range (1 , 100) , number = >


6 {
7 dict . TryAdd ( number , $ " Value { number } " ) ;
8 }) ;

How does the ‘ThreadPool‘ improve thread management in .NET?


The ‘ThreadPool‘ in .NET manages a pool of reusable threads to perform short-lived tasks, re-
ducing the overhead of creating and destroying threads. When a task completes, the thread is

492
11.2. Thread Management

returned to the pool for reuse.

Listing 11.29: Code example

1 // Example : Queuing a task to the ThreadPool


2 ThreadPool . QueueUserWorkItem ( state = >
3 {
4 Console . WriteLine ( " Work item executed by ThreadPool . " ) ;
5 }) ;

What is a ‘CancellationToken‘, and how is it used to manage thread


cancellation?
A ‘CancellationToken‘ is used to signal that an operation should be canceled. It is passed to
threads or tasks to enable cooperative cancellation, allowing threads to gracefully exit when they
detect the cancellation signal.

Listing 11.30: Code example

1 // Example : Using CancellationToken to cancel a long - running task


2 public async Task RunTaskWithCancellationAsync ( CancellationToken token )
3 {
4 for ( int i = 0; i < 100; i ++)
5 {
6 token . ThrowIfCancellationRequested () ;
7 await Task . Delay (100) ; // Simulate work
8 }
9 }

What is ‘Thread.Join()‘, and when should it be used?


‘Thread.Join()‘ is used to block the calling thread until the specified thread completes execution.
It is useful when you need to ensure that a thread finishes its work before proceeding with other
operations in the calling thread.

Listing 11.31: Code example

1 // Example : Using Thread . Join to wait for a thread to finish


2 Thread thread = new Thread (() = >
3 {
4 Console . WriteLine ( " Thread is running . " ) ;
5 }) ;
6 thread . Start () ;
7 thread . Join () ; // Wait for the thread to complete
8 Console . WriteLine ( " Thread completed . " ) ;

493
11.2. Thread Management

What is a deadlock in thread management, and how can it be prevented?


A deadlock occurs when two or more threads are waiting on each other to release resources, causing
both threads to be stuck indefinitely. Deadlocks can be prevented by ensuring that locks are always
acquired in the same order and by using ‘Monitor.TryEnter‘ with timeouts.

Listing 11.32: Code example

1 // Example : Avoiding deadlock by ensuring a consistent locking order


2 private readonly object _lock1 = new object () ;
3 private readonly object _lock2 = new object () ;
4

5 public void MethodA ()


6 {
7 lock ( _lock1 )
8 {
9 lock ( _lock2 )
10 {
11 // Critical section
12 }
13 }
14 }

How does the ‘SemaphoreSlim‘ class help in managing thread synchro-


nization?
‘SemaphoreSlim‘ is a lightweight synchronization primitive used to limit the number of threads
that can access a resource simultaneously. Unlike a ‘Mutex‘, ‘SemaphoreSlim‘ allows multiple
threads to enter, up to a specified limit.

Listing 11.33: Code example

1 // Example : Using SemaphoreSlim to manage concurrent access


2 SemaphoreSlim semaphore = new SemaphoreSlim (2) ; // Maximum 2 threads can access
3

4 public async Task AccessResourceAsync ()


5 {
6 await semaphore . WaitAsync () ;
7 try
8 {
9 // Critical section
10 }
11 finally
12 {
13 semaphore . Release () ; // Release the semaphore
14 }
15 }

494
11.2. Thread Management

What is ‘Monitor.Wait‘ and ‘Monitor.Pulse‘, and how are they used in


thread synchronization?
‘Monitor.Wait‘ releases the lock and waits for a signal from another thread. ‘Monitor.Pulse‘ or
‘Monitor.PulseAll‘ signals waiting threads to proceed. These are used together to coordinate
communication between threads.

Listing 11.34: Code example

1 // Example : Using Monitor . Wait and Monitor . Pulse for thread communication
2 private readonly object _lock = new object () ;
3 private bool _ready = false ;
4

5 public void WaitForSignal ()


6 {
7 lock ( _lock )
8 {
9 while (! _ready )
10 {
11 Monitor . Wait ( _lock ) ; // Wait until a signal is received
12 }
13 Console . WriteLine ( " Signal received . " ) ;
14 }
15 }
16

17 public void SendSignal ()


18 {
19 lock ( _lock )
20 {
21 _ready = true ;
22 Monitor . Pulse ( _lock ) ; // Signal the waiting thread
23 }
24 }

What is the difference between ‘Semaphore‘ and ‘SemaphoreSlim‘ in


.NET?
‘Semaphore‘ can be used for cross-process synchronization, while ‘SemaphoreSlim‘ is optimized for
in-process use and provides better performance. ‘SemaphoreSlim‘ should be preferred when you
do not need to synchronize across processes.

Listing 11.35: Code example

1 // Example : Using SemaphoreSlim for in - process synchronization


2 SemaphoreSlim semaphoreSlim = new SemaphoreSlim (1 , 1) ; // Only one thread can
enter

495
11.2. Thread Management

4 public async Task AccessCriticalSectionAsync ()


5 {
6 await semaphoreSlim . WaitAsync () ;
7 try
8 {
9 // Critical section
10 }
11 finally
12 {
13 semaphoreSlim . Release () ;
14 }
15 }

How do you manage thread affinity in .NET, and when would it be nec-
essary?
Thread affinity refers to binding a thread to a specific processor core. In .NET, thread affinity
can be managed using the ‘ProcessThread.ProcessorAffinity‘ property. It is necessary in scenarios
where cache locality is important, such as in real-time systems.

Listing 11.36: Code example

1 // Example : Setting thread affinity to a specific processor core


2 ProcessThread currentThread = Process . GetCurrentProcess () . Threads [0];
3 currentThread . ProcessorAffinity = ( IntPtr ) 1; // Bind thread to the first CPU core

What is the ‘Task.Run‘ method, and how does it manage threads differ-
ently from the ‘Thread‘ class?
‘Task.Run‘ is a higher-level abstraction over threads and uses the ‘ThreadPool‘ to manage threads.
It is preferred for CPU-bound and short-lived tasks because it efficiently manages threads without
the overhead of directly managing individual threads as with the ‘Thread‘ class.

Listing 11.37: Code example

1 // Example : Using Task . Run to offload work to the ThreadPool


2 public async Task < int > PerformComputationAsync ( int a , int b )
3 {
4 return await Task . Run (() = > a + b ) ; // Offload work to a background thread
5 }

496
11.2. Thread Management

How can you handle exceptions that occur on a background thread in


.NET?
Exceptions in background threads should be caught using try-catch blocks inside the thread. When
using tasks, unhandled exceptions can be observed using the ‘Task.Exception‘ property or by
attaching a continuation with ‘Task.ContinueWith‘.
Listing 11.38: Code example

1 // Example : Handling exceptions in a background thread


2 Thread thread = new Thread (() = >
3 {
4 try
5 {
6 throw new Exception ( " Error occurred in thread . " ) ;
7 }
8 catch ( Exception ex )
9 {
10 Console . WriteLine ( $ " Exception caught : { ex . Message } " ) ;
11 }
12 }) ;
13 thread . Start () ;

What is a thread pool, and why is it preferred over manually managing


threads in .NET applications?
A thread pool is a collection of reusable threads that can be used to perform tasks without the
overhead of creating and destroying threads. The ‘ThreadPool‘ manages the lifecycle of threads,
making it efficient for handling multiple short-lived tasks, as opposed to manually creating and
destroying threads.
Listing 11.39: Code example

1 // Example : Queueing a work item to the ThreadPool


2 ThreadPool . QueueUserWorkItem ( state = >
3 {
4 Console . WriteLine ( " Task executed by ThreadPool . " ) ;
5 }) ;

How do you measure the performance of threads in .NET, and what tools
are available?
Thread performance in .NET can be measured using tools such as the Visual Studio Profiler,
PerfView, or the Windows Performance Monitor. Metrics like CPU usage, thread count, and
execution time can help identify bottlenecks or inefficiencies.

497
11.3. Asynchronous Programming

Listing 11.40: Code example

1 // No direct C # code for performance measurement , but tools like the Visual Studio
Profiler or PerfView can analyze thread performance .

11.3 Asynchronous Programming


What is asynchronous programming, and why is it important in .NET?
Asynchronous programming allows a program to execute other tasks while waiting for an operation
to complete, improving responsiveness and performance, especially in I/O-bound tasks. In .NET,
‘async‘ and ‘await‘ are the primary keywords used for implementing asynchronous programming.

Listing 11.41: Code example

1 // Example : Asynchronous method using async / await


2 public async Task < string > FetchDataAsync ()
3 {
4 // Simulating an asynchronous I / O operation
5 await Task . Delay (1000) ;
6 return " Data fetched " ;
7 }

How does ‘async‘ and ‘await‘ work in .NET, and what does it mean to
mark a method as ‘async‘ ?
In .NET, marking a method as ‘async‘ allows it to contain ‘await‘ expressions, which asyn-
chronously wait for a task to complete without blocking the thread. The ‘async‘ keyword does not
run the method asynchronously but enables the use of ‘await‘ within the method.

Listing 11.42: Code example

1 // Example : Using async and await


2 public async Task < int > CalculateSumAsync ( int a , int b )
3 {
4 await Task . Delay (1000) ; // Simulating a delay
5 return a + b ;
6 }

What are ‘Task‘ and ‘Task<T>‘, and how are they used in asynchronous
programming?
‘Task‘ represents an asynchronous operation that does not return a result, while ‘Task<T>‘ repre-
sents an operation that returns a result of type ‘T‘. These classes are fundamental for async/await

498
11.3. Asynchronous Programming

programming in .NET.

Listing 11.43: Code example

1 // Example : Using Task and Task <T > in asynchronous programming


2 public async Task < int > GetNumberAsync ()
3 {
4 return await Task . FromResult (42) ; // Task <T > returns a result
5 }

What are the differences between synchronous and asynchronous pro-


gramming in terms of performance and scalability?
Synchronous programming waits for a task to complete before continuing, which can block the
thread and degrade performance, especially for I/O-bound tasks. Asynchronous programming
allows other tasks to run while waiting, improving performance and scalability by freeing up
threads.

Listing 11.44: Code example

1 // Example : Synchronous vs asynchronous programming


2 public int GetDataSync ()
3 {
4 Thread . Sleep (1000) ; // Blocking the thread
5 return 42;
6 }
7

8 public async Task < int > GetDataAsync ()


9 {
10 await Task . Delay (1000) ; // Non - blocking
11 return 42;
12 }

What is the ‘ConfigureAwait‘ method, and when should you use it in


asynchronous programming?
‘ConfigureAwait‘ is used to specify whether the continuation after ‘await‘ should be run on the
original synchronization context (e.g., UI thread). ‘ConfigureAwait(false)‘ is commonly used in
libraries to avoid capturing the context and to allow continuation on any available thread.

Listing 11.45: Code example

1 // Example : Using ConfigureAwait in async programming


2 public async Task < int > FetchDataAsync ()
3 {
4 await Task . Delay (1000) . ConfigureAwait ( false ) ; // Do not capture the context

499
11.3. Asynchronous Programming

5 return 42;
6 }

What are the common pitfalls of asynchronous programming, such as


deadlocks and thread starvation?
Common pitfalls include deadlocks, which can occur if a UI thread synchronously waits for an async
task to complete (e.g., ‘Task.Result‘), and thread starvation, which can happen if asynchronous
tasks are queued too frequently, leading to excessive use of the thread pool.

Listing 11.46: Code example

1 // Example : Deadlock scenario ( do not do this )


2 public void DeadlockExample ()
3 {
4 var result = GetDataAsync () . Result ; // Causes deadlock if called on UI thread
5 }
6

7 // Correct approach : Use await instead


8 public async Task < int > SafeGetDataAsync ()
9 {
10 return await GetDataAsync () ;
11 }

How does ‘async void‘ differ from ‘async Task‘, and why should ‘async
void‘ be avoided?
‘async void‘ is used for event handlers and does not return a ‘Task‘, making it impossible to handle
exceptions or await its completion. ‘async Task‘ should be used for most asynchronous methods
because it allows proper exception handling and awaiting.

Listing 11.47: Code example

1 // Example : Avoid async void ; prefer async Task


2 public async Task PerformTaskAsync ()
3 {
4 await Task . Delay (1000) ; // Task can be awaited
5 }
6

7 public async void PerformTaskVoidAsync () // Avoid this pattern


8 {
9 await Task . Delay (1000) ; // Exceptions are unhandled
10 }

500
11.3. Asynchronous Programming

What is the purpose of ‘Task.WhenAll‘ and ‘Task.WhenAny‘, and how


do they work?
‘Task.WhenAll‘ waits for all provided tasks to complete, while ‘Task.WhenAny‘ returns the first
task to complete. Both methods allow for better management of multiple asynchronous operations.

Listing 11.48: Code example

1 // Example : Using Task . WhenAll and Task . WhenAny


2 public async Task RunMultipleTasksAsync ()
3 {
4 var task1 = Task . Delay (1000) ;
5 var task2 = Task . Delay (2000) ;
6

7 await Task . WhenAll ( task1 , task2 ) ; // Wait for all tasks to complete
8

9 var firstCompletedTask = await Task . WhenAny ( task1 , task2 ) ; // Wait for the
first task to complete
10 }

What is ‘CancellationToken‘, and how is it used to cancel asynchronous


operations?
‘CancellationToken‘ is used to signal cancellation of an asynchronous operation. It is passed as a
parameter to async methods and can be checked or thrown to cancel long-running tasks.

Listing 11.49: Code example

1 // Example : Using CancellationToken to cancel an async operation


2 public async Task PerformTaskWithCancellationAsync ( CancellationToken token )
3 {
4 for ( int i = 0; i < 10; i ++)
5 {
6 token . ThrowIfCancellationRequested () ;
7 await Task . Delay (500) ; // Simulate work
8 }
9 }

What is ‘Task.Delay‘, and how does it differ from ‘Thread.Sleep‘ ?


‘Task.Delay‘ is an asynchronous method that pauses execution without blocking the thread, al-
lowing other tasks to run. ‘Thread.Sleep‘ blocks the current thread, preventing other tasks from
executing.

Listing 11.50: Code example

501
11.3. Asynchronous Programming

1 // Example : Task . Delay vs Thread . Sleep


2 public async Task NonBlockingDelayAsync ()
3 {
4 await Task . Delay (1000) ; // Non - blocking
5 }
6

7 public void BlockingDelay ()


8 {
9 Thread . Sleep (1000) ; // Blocks the thread
10 }

How do ‘async‘ and ‘await‘ help avoid callback hell in .NET asynchronous
programming?
‘async‘ and ‘await‘ eliminate the need for deeply nested callbacks, making asynchronous code more
readable and maintainable. Instead of passing callbacks, developers can write asynchronous code
sequentially.

Listing 11.51: Code example

1 // Example : Async / await vs callback hell


2 public async Task < string > GetDataAsync ()
3 {
4 var result1 = await FetchDataFromApiAsync () ;
5 var result2 = await FetchDataFromDatabaseAsync () ;
6 return result1 + result2 ; // Sequential , readable flow
7 }

What is the ‘async‘ method’s return type, and why can’t it return ‘void‘
(except for event handlers)?
The return type of an ‘async‘ method should be ‘Task‘ or ‘Task<T>‘. Returning ‘void‘ should
only be done for event handlers, as ‘void‘ does not allow the caller to await the completion or
handle exceptions.

Listing 11.52: Code example

1 // Example : Correct async method return types


2 public async Task PerformTaskAsync ()
3 {
4 await Task . Delay (1000) ;
5 }
6

7 public async Task < int > GetValueAsync ()


8 {

502
11.3. Asynchronous Programming

9 await Task . Delay (1000) ;


10 return 42;
11 }

How do you handle exceptions in asynchronous methods in .NET?


Exceptions in asynchronous methods are automatically captured and propagated to the ‘await‘
call. You can handle exceptions using try-catch around ‘await‘, or observe unhandled exceptions
in tasks via the ‘Task.Exception‘ property.
Listing 11.53: Code example

1 // Example : Handling exceptions in async methods


2 public async Task < int > SafeGetValueAsync ()
3 {
4 try
5 {
6 return await Task . FromResult (42) ;
7 }
8 catch ( Exception ex )
9 {
10 Console . WriteLine ( $ " Error : { ex . Message } " ) ;
11 return -1;
12 }
13 }

What is the purpose of ‘ValueTask‘ in asynchronous programming, and


when should it be used instead of ‘Task‘ ?
‘ValueTask‘ is a struct-based alternative to ‘Task‘ that can improve performance when the result
is available synchronously. It should be used when the operation completes synchronously most of
the time, as it avoids heap allocations associated with ‘Task‘.
Listing 11.54: Code example

1 // Example : Using ValueTask in an async method


2 public async ValueTask < int > GetValueAsync ( bool isFast )
3 {
4 if ( isFast )
5 {
6 return 42; // Synchronous completion
7 }
8

9 await Task . Delay (1000) ; // Asynchronous completion


10 return 42;
11 }

503
11.3. Asynchronous Programming

How does ‘Task.Run‘ differ from ‘Task.Factory.StartNew‘, when should


you use each in asynchronous programming?
‘Task.Run‘ is a simpler, higher-level method for running CPU-bound work on a background thread.
‘Task.Factory.StartNew‘ offers more control over task creation options (e.g., scheduling) but is
generally not recommended unless specific task settings are needed.

Listing 11.55: Code example

1 // Example : Using Task . Run for CPU - bound work


2 public async Task < int > PerformComputationAsync ()
3 {
4 return await Task . Run (() = >
5 {
6 // Simulate CPU - bound work
7 return 42;
8 }) ;
9 }

How can you optimize thread pool usage in asynchronous programming?


You can optimize thread pool usage by avoiding unnecessary ‘Task.Run‘ or blocking operations,
using ‘ConfigureAwait(false)‘ to avoid context capturing, and using asynchronous I/O operations
to free up threads for other tasks.

Listing 11.56: Code example

1 // Example : Optimizing thread pool usage with ConfigureAwait ( false )


2 public async Task < int > FetchDataOptimizedAsync ()
3 {
4 await Task . Delay (1000) . ConfigureAwait ( false ) ; // Avoid context capture
5 return 42;
6 }

What is a continuation task, and how do you implement it in .NET?


A continuation task is a task that runs after another task completes. Continuations can be added
using ‘Task.ContinueWith‘, allowing developers to specify code that should execute after the first
task.

Listing 11.57: Code example

1 // Example : Adding a continuation task


2 public void RunWithContinuation ()
3 {
4 Task . Run (() = >

504
11.3. Asynchronous Programming

5 {
6 Console . WriteLine ( " First task running . " ) ;
7 }) . ContinueWith ( prevTask = >
8 {
9 Console . WriteLine ( " Continuation task running . " ) ;
10 }) ;
11 }

How does asynchronous programming affect scalability in web applica-


tions?
Asynchronous programming improves scalability by freeing up threads for other tasks while waiting
for I/O operations (e.g., database calls, web requests). This allows a web server to handle more
requests concurrently without blocking threads.

Listing 11.58: Code example

1 // Example : Asynchronous method in an ASP . NET Core controller


2 public async Task < IActionResult > GetDataAsync ()
3 {
4 var data = await FetchDataAsync () ; // Non - blocking I / O
5 return Ok ( data ) ;
6 }

How do you use ‘Task.Yield‘, and when is it appropriate in asynchronous


programming?
‘Task.Yield‘ is used to force the current method to yield control back to the caller, continuing the
method asynchronously. It is useful when you need to ensure that the method does not block the
UI or prevent a long-running operation from monopolizing the thread.

Listing 11.59: Code example

1 // Example : Using Task . Yield to yield control


2 public async Task PerformLongRunningTaskAsync ()
3 {
4 await Task . Yield () ; // Yield control back to the caller
5

6 for ( int i = 0; i < 100; i ++)


7 {
8 // Simulate work
9 }
10 }

505
11.4. Synchronization Primitives

11.4 Synchronization Primitives


What are synchronization primitives in .NET, and why are they impor-
tant?
Synchronization primitives are low-level mechanisms that control access to shared resources in
multithreaded environments, preventing race conditions and ensuring data integrity. Examples
include ‘lock‘, ‘Monitor‘, ‘SemaphoreSlim‘, ‘Mutex‘, and ‘AutoResetEvent‘.

Listing 11.60: Code example

1 // Example : Using lock for synchronization


2 private readonly object _syncLock = new object () ;
3 private int _sharedResource ;
4

5 public void UpdateResource ()


6 {
7 lock ( _syncLock ) // Prevent multiple threads from accessing simultaneously
8 {
9 _sharedResource ++;
10 }
11 }

What is the ‘lock‘ statement in C#, and how does it work?


The ‘lock‘ statement is a shorthand for ‘Monitor.Enter‘ and ‘Monitor.Exit‘. It ensures that only
one thread can access the critical section of code at a time. The lock is released when the thread
leaves the critical section or an exception occurs.

Listing 11.61: Code example

1 // Example : Using lock to synchronize access to shared data


2 private readonly object _lockObject = new object () ;
3

4 public void SafeIncrement ( ref int counter )


5 {
6 lock ( _lockObject )
7 {
8 counter ++;
9 }
10 }

506
11.4. Synchronization Primitives

What is the difference between ‘Monitor‘ and ‘lock‘, and when should
you use each?
‘lock‘ is a syntactic sugar for ‘Monitor.Enter‘ and ‘Monitor.Exit‘, making it simpler to use. How-
ever, ‘Monitor‘ provides more flexibility with methods like ‘Monitor.TryEnter‘, ‘Monitor.Pulse‘,
and ‘Monitor.Wait‘, giving fine-grained control over thread synchronization.

Listing 11.62: Code example

1 // Example : Using Monitor . TryEnter to avoid blocking


2 private readonly object _lockObject = new object () ;
3

4 public void TrySafeIncrement ( ref int counter )


5 {
6 if ( Monitor . TryEnter ( _lockObject ) )
7 {
8 try
9 {
10 counter ++;
11 }
12 finally
13 {
14 Monitor . Exit ( _lockObject ) ;
15 }
16 }
17 else
18 {
19 Console . WriteLine ( " Lock not acquired . " ) ;
20 }
21 }

What is a ‘Mutex‘, and how does it differ from ‘lock‘ or ‘Monitor‘ ?


A ‘Mutex‘ is a synchronization primitive used for inter-process synchronization, allowing coordi-
nation across multiple processes. Unlike ‘lock‘ and ‘Monitor‘, which work within a single process,
a ‘Mutex‘ can synchronize threads in different processes.

Listing 11.63: Code example

1 // Example : Using Mutex for cross - process synchronization


2 Mutex mutex = new Mutex ( false , " GlobalMutex " ) ;
3

4 public void UseMutex ()


5 {
6 mutex . WaitOne () ; // Acquire the mutex
7

8 try

507
11.4. Synchronization Primitives

9 {
10 // Critical section
11 }
12 finally
13 {
14 mutex . ReleaseMutex () ; // Release the mutex
15 }
16 }

How ‘SemaphoreSlim‘ work, and how does it differ from a ‘Semaphore‘ ?


‘SemaphoreSlim‘ is a lightweight version of ‘Semaphore‘ that is optimized for in-process use.
Both allow multiple threads to enter the critical section but up to a specified maximum num-
ber. ‘SemaphoreSlim‘ is faster and should be used unless cross-process synchronization is needed.

Listing 11.64: Code example

1 // Example : Using SemaphoreSlim to limit concurrent access


2 SemaphoreSlim semaphore = new SemaphoreSlim (3) ; // Limit to 3 threads
3

4 public async Task UseResourceAsync ()


5 {
6 await semaphore . WaitAsync () ; // Wait to enter
7

8 try
9 {
10 // Critical section
11 }
12 finally
13 {
14 semaphore . Release () ; // Release the semaphore
15 }
16 }

What is an ‘AutoResetEvent‘, and how is it used for thread synchroniza-


tion?
An ‘AutoResetEvent‘ is a signaling mechanism that allows one thread to signal another thread that
it can proceed. It automatically resets after a waiting thread is released, so subsequent threads
must wait for another signal.

Listing 11.65: Code example

1 // Example : Using AutoResetEvent to signal between threads


2 AutoResetEvent autoResetEvent = new AutoResetEvent ( false ) ;
3

508
11.4. Synchronization Primitives

4 public void WaitForSignal ()


5 {
6 Console . WriteLine ( " Waiting for signal ... " ) ;
7 autoResetEvent . WaitOne () ; // Wait until signaled
8 Console . WriteLine ( " Signal received . " ) ;
9 }
10

11 public void SendSignal ()


12 {
13 Console . WriteLine ( " Sending signal ... " ) ;
14 autoResetEvent . Set () ; // Signal the waiting thread
15 }

How does a ‘ManualResetEvent‘ differ from an ‘AutoResetEvent‘, and


when should it be used?
‘ManualResetEvent‘ allows multiple threads to be released once it is signaled, as it remains signaled
until manually reset using the ‘Reset()‘ method. In contrast, ‘AutoResetEvent‘ releases only one
waiting thread and automatically resets itself.

Listing 11.66: Code example

1 // Example : Using ManualResetEvent for thread signaling


2 ManualResetEvent manualResetEvent = new ManualResetEvent ( false ) ;
3

4 public void WaitForSignal ()


5 {
6 Console . WriteLine ( " Waiting for signal ... " ) ;
7 manualResetEvent . WaitOne () ; // Wait until signaled
8 Console . WriteLine ( " Signal received . " ) ;
9 }
10

11 public void SendSignal ()


12 {
13 Console . WriteLine ( " Sending signal ... " ) ;
14 manualResetEvent . Set () ; // Signal all waiting threads
15 }

What is a ‘Barrier‘, and how does it help with multithreaded coordina-


tion?
A ‘Barrier‘ is a synchronization primitive that allows multiple threads to wait until all threads
have reached a certain point. It is useful for coordinating the execution of multiple threads at
various stages.

509
11.4. Synchronization Primitives

Listing 11.67: Code example

1 // Example : Using Barrier to synchronize threads


2 Barrier barrier = new Barrier (3 , ( b ) = >
3 {
4 Console . WriteLine ( $ " Phase { b . CurrentPhaseNumber } completed . " ) ;
5 }) ;
6

7 public void DoWork ( int threadId )


8 {
9 Console . WriteLine ( $ " Thread { threadId } reached the barrier . " ) ;
10 barrier . SignalAndWait () ; // Wait for other threads to reach the barrier
11 Console . WriteLine ( $ " Thread { threadId } passed the barrier . " ) ;
12 }

What is a ‘SpinLock‘, and when would you use it in .NET?


‘SpinLock‘ is a low-level synchronization primitive that repeatedly checks for a lock until it can be
acquired. It is useful for short, highly-contended locks where the overhead of kernel-based locking
(like ‘Monitor‘) is too high.
Listing 11.68: Code example

1 // Example : Using SpinLock for low - latency synchronization


2 SpinLock spinLock = new SpinLock () ;
3

4 public void UseSpinLock ()


5 {
6 bool lockTaken = false ;
7 try
8 {
9 spinLock . Enter ( ref lockTaken ) ;
10 // Critical section
11 }
12 finally
13 {
14 if ( lockTaken )
15 {
16 spinLock . Exit () ;
17 }
18 }
19 }

What is a ‘ReaderWriterLockSlim‘, and when is it beneficial to use?


‘ReaderWriterLockSlim‘ allows multiple threads to read a shared resource simultaneously, but only
one thread to write to it. It is useful when read operations are more frequent than write operations,

510
11.4. Synchronization Primitives

improving performance by reducing contention.

Listing 11.69: Code example

1 // Example : Using ReaderWriterLockSlim for read / write synchronization


2 ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim () ;
3

4 public void ReadData ()


5 {
6 rwLock . EnterReadLock () ;
7 try
8 {
9 // Read data
10 }
11 finally
12 {
13 rwLock . ExitReadLock () ;
14 }
15 }
16

17 public void WriteData ()


18 {
19 rwLock . EnterWriteLock () ;
20 try
21 {
22 // Write data
23 }
24 finally
25 {
26 rwLock . ExitWriteLock () ;
27 }
28 }

How does ‘Monitor.Pulse‘ and ‘Monitor.Wait‘ work together in thread


synchronization?
‘Monitor.Wait‘ releases the lock and waits for a signal (‘Monitor.Pulse‘ or ‘Monitor.PulseAll‘)
to proceed. This mechanism is used for thread communication where one thread must wait for
another thread to signal it.

Listing 11.70: Code example

1 // Example : Using Monitor . Wait and Monitor . Pulse for thread coordination
2 private readonly object _lock = new object () ;
3 private bool _ready = false ;
4

5 public void WaitForSignal ()

511
11.4. Synchronization Primitives

6 {
7 lock ( _lock )
8 {
9 while (! _ready )
10 {
11 Monitor . Wait ( _lock ) ; // Wait for a signal
12 }
13 Console . WriteLine ( " Signal received . " ) ;
14 }
15 }
16

17 public void SendSignal ()


18 {
19 lock ( _lock )
20 {
21 _ready = true ;
22 Monitor . Pulse ( _lock ) ; // Signal the waiting thread
23 }
24 }

How does ‘CountdownEvent‘ work, and what is its use case in multi-
threaded programming?
‘CountdownEvent‘ is a synchronization primitive that blocks one or more threads until the counter
reaches zero. It is useful when multiple threads need to signal the completion of their work before
the next step can proceed.

Listing 11.71: Code example

1 // Example : Using CountdownEvent to wait for multiple tasks to complete


2 CountdownEvent countdown = new CountdownEvent (3) ;
3

4 public void DoWork ()


5 {
6 // Perform work
7 countdown . Signal () ; // Signal work is complete
8 }
9

10 public void WaitForAll ()


11 {
12 countdown . Wait () ; // Wait until all tasks signal completion
13 }

512
11.4. Synchronization Primitives

What is the purpose of ‘Interlocked‘ class, and how does it differ from
other synchronization primitives?
The ‘Interlocked‘ class provides atomic operations for incrementing, decrementing, and exchanging
values, avoiding the overhead of locking. It is suitable for simple operations where performance is
critical and locks are not necessary.

Listing 11.72: Code example

1 // Example : Using Interlocked for atomic operations


2 private int _counter = 0;
3

4 public void IncrementCounter ()


5 {
6 Interlocked . Increment ( ref _counter ) ; // Atomically increment the counter
7 }

What is ‘SpinWait‘, and how does it differ from ‘SpinLock‘ in .NET?


‘SpinWait‘ is a low-level primitive that causes a thread to spin for a certain number of iterations
before yielding. It is typically used in tight loops where you expect the resource to become available
very soon. Unlike ‘SpinLock‘, it doesn’t lock any resource but only delays execution.

Listing 11.73: Code example

1 // Example : Using SpinWait to delay execution


2 SpinWait spinWait = new SpinWait () ;
3 while (! conditionMet )
4 {
5 spinWait . SpinOnce () ; // Perform a busy - wait spin
6 }

How does a ‘CountdownEvent‘ differ from a ‘Barrier‘, and when would


you use each?
A ‘CountdownEvent‘ counts down to zero, signaling that all operations are complete. In contrast,
a ‘Barrier‘ synchronizes multiple threads at different stages of execution. Use ‘CountdownEvent‘
when waiting for multiple operations to finish, and use ‘Barrier‘ for coordinating phases.

Listing 11.74: Code example

1 // Example : Using CountdownEvent to wait for all threads


2 CountdownEvent countdown = new CountdownEvent (3) ;
3

4 public void DoWork ()


5 {

513
11.4. Synchronization Primitives

6 // Perform work
7 countdown . Signal () ; // Signal completion
8 }
9

10 public void WaitForAll ()


11 {
12 countdown . Wait () ; // Wait until all tasks finish
13 }

How does ‘SemaphoreSlim‘ work, and how can it help control access to
resources?
‘SemaphoreSlim‘ controls access to a shared resource by allowing a specific number of threads to
enter. Once the limit is reached, additional threads must wait until a thread releases the semaphore,
ensuring controlled access to the resource.

Listing 11.75: Code example

1 // Example : Using SemaphoreSlim to manage resource access


2 SemaphoreSlim semaphore = new SemaphoreSlim (2) ; // Limit to 2 concurrent threads
3

4 public async Task AccessResourceAsync ()


5 {
6 await semaphore . WaitAsync () ; // Wait to enter
7

8 try
9 {
10 // Critical section
11 }
12 finally
13 {
14 semaphore . Release () ; // Release the semaphore
15 }
16 }

What is a ‘ManualResetEventSlim‘, and how is it different from ‘Manu-


alResetEvent‘ ?
‘ManualResetEventSlim‘ is a lightweight version of ‘ManualResetEvent‘ optimized for in-process
signaling. It provides better performance when signaling is only needed within a single process
and offers lower overhead than ‘ManualResetEvent‘.

Listing 11.76: Code example

1 // Example : Using ManualResetEventSlim for in - process signaling

514
11.4. Synchronization Primitives

2 ManualResetEventSlim manualReset = new ManualResetEventSlim ( false ) ;


3

4 public void WaitForSignal ()


5 {
6 Console . WriteLine ( " Waiting for signal ... " ) ;
7 manualReset . Wait () ; // Wait until signaled
8 Console . WriteLine ( " Signal received . " ) ;
9 }
10

11 public void SendSignal ()


12 {
13 Console . WriteLine ( " Sending signal ... " ) ;
14 manualReset . Set () ; // Signal all waiting threads
15 }

How does ‘TaskCompletionSource‘ help in asynchronous synchroniza-


tion?
‘TaskCompletionSource‘ allows you to create and manually complete a ‘Task‘. It is useful for
integrating event-based or callback-based APIs into the ‘async/await‘ model, allowing you to signal
task completion from external sources.
Listing 11.77: Code example

1 // Example : Using TaskCompletionSource to signal task completion


2 TaskCompletionSource < int > tcs = new TaskCompletionSource < int >() ;
3

4 public Task < int > GetDataAsync ()


5 {
6 // Simulate an external event that completes the task
7 Task . Run (() = >
8 {
9 Thread . Sleep (1000) ;
10 tcs . SetResult (42) ; // Signal completion
11 }) ;
12

13 return tcs . Task ; // Return the task


14 }

What are the advantages of using ‘ReaderWriterLockSlim‘ over ‘lock‘ for


read-heavy operations?
‘ReaderWriterLockSlim‘ allows multiple threads to read concurrently while ensuring exclusive ac-
cess for writes. This improves performance in scenarios where reads are more frequent than writes,
compared to ‘lock‘, which blocks both reads and writes.

515
11.5. Advanced Concurrency and Multithreading

Listing 11.78: Code example

1 // Example : Using ReaderWriterLockSlim for read - heavy scenarios


2 ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim () ;
3

4 public void ReadData ()


5 {
6 rwLock . EnterReadLock () ;
7 try
8 {
9 // Read data
10 }
11 finally
12 {
13 rwLock . ExitReadLock () ;
14 }
15 }
16

17 public void WriteData ()


18 {
19 rwLock . EnterWriteLock () ;
20 try
21 {
22 // Write data
23 }
24 finally
25 {
26 rwLock . ExitWriteLock () ;
27 }
28 }

11.5 Advanced Concurrency and Multithreading


What is lock contention, and how can it affect multithreaded perfor-
mance?
Lock contention occurs when multiple threads attempt to acquire the same lock simultaneously,
causing performance degradation as they wait for the lock to be released. It can lead to higher
CPU usage and decreased throughput in a multithreaded application.

Listing 11.79: Code example

1 // Example : Reducing lock contention by using fine - grained locking


2 private readonly object _lock1 = new object () ;
3 private readonly object _lock2 = new object () ;
4

516
11.5. Advanced Concurrency and Multithreading

5 public void UpdateResources ()


6 {
7 lock ( _lock1 )
8 {
9 // Update resource 1
10 }
11

12 lock ( _lock2 )
13 {
14 // Update resource 2
15 }
16 }

How does the ‘ConcurrentDictionary‘ help in reducing lock contention?


‘ConcurrentDictionary‘ is a thread-safe collection that reduces lock contention by using fine-grained
locking internally, allowing multiple threads to perform read and write operations simultaneously
without the need for external locks.

Listing 11.80: Code example

1 // Example : Using ConcurrentDictionary for thread - safe operations


2 ConcurrentDictionary < int , string > concurrentDict = new ConcurrentDictionary < int ,
string >() ;
3 concurrentDict . TryAdd (1 , " Value1 " ) ;
4

5 Parallel . ForEach ( Enumerable . Range (1 , 100) , number = >


6 {
7 concurrentDict . TryAdd ( number , $ " Value { number } " ) ;
8 }) ;

What is a ‘SpinLock‘, and when would you prefer it over traditional


locking mechanisms?
A ‘SpinLock‘ is a low-level synchronization primitive that repeatedly checks for a lock in a tight
loop without yielding the thread. It is useful in situations where the lock is held for very short
durations, as it avoids the overhead of context switching associated with kernel locks.

Listing 11.81: Code example

1 // Example : Using SpinLock for low - latency synchronization


2 SpinLock spinLock = new SpinLock () ;
3

4 public void PerformSpinLockOperation ()


5 {
6 bool lockTaken = false ;

517
11.5. Advanced Concurrency and Multithreading

7 try
8 {
9 spinLock . Enter ( ref lockTaken ) ;
10 // Critical section
11 }
12 finally
13 {
14 if ( lockTaken )
15 {
16 spinLock . Exit () ;
17 }
18 }
19 }

What is a ‘ThreadLocal<T>‘ variable, and how can it improve perfor-


mance in multithreaded scenarios?
‘ThreadLocal<T>‘ provides thread-specific storage for variables, meaning each thread gets its own
instance of the variable. This avoids synchronization overhead, as each thread can work with its
own copy of the data without contention.

Listing 11.82: Code example

1 // Example : Using ThreadLocal for thread - specific data


2 ThreadLocal < int > threadLocalData = new ThreadLocal < int >(() = > Thread . CurrentThread
. ManagedThreadId ) ;
3

4 public void PrintThreadLocalData ()


5 {
6 Console . WriteLine ( $ " Thread ID : { threadLocalData . Value } " ) ;
7 }

What is a deadlock, and how can you prevent it in complex multithreaded


systems?
A deadlock occurs when two or more threads are waiting for each other to release resources, causing
them to be stuck indefinitely. Deadlocks can be prevented by ensuring a consistent locking order,
using timeouts for locks, and avoiding circular dependencies between locks.

Listing 11.83: Code example

1 // Example : Preventing deadlocks by maintaining a consistent locking order


2 private readonly object _lock1 = new object () ;
3 private readonly object _lock2 = new object () ;
4

518
11.5. Advanced Concurrency and Multithreading

5 public void SafeMethodA ()


6 {
7 lock ( _lock1 )
8 {
9 lock ( _lock2 )
10 {
11 // Critical section
12 }
13 }
14 }

What is thread starvation, and how can it be mitigated in a multi-


threaded environment?
Thread starvation occurs when a thread is unable to acquire the necessary resources (e.g., a lock)
due to higher-priority threads constantly acquiring them first. It can be mitigated by using fair
scheduling algorithms, prioritizing locks, and avoiding long-held locks.

Listing 11.84: Code example

1 // Example : Using a fair SemaphoreSlim to prevent starvation


2 SemaphoreSlim semaphore = new SemaphoreSlim (1 , 1) ;
3

4 public async Task UseResourceAsync ()


5 {
6 await semaphore . WaitAsync () ; // Fairly distributed access
7 try
8 {
9 // Critical section
10 }
11 finally
12 {
13 semaphore . Release () ;
14 }
15 }

How do you use ‘CancellationToken‘ to handle task cancellation in .NET?


‘CancellationToken‘ allows tasks to be canceled cooperatively. It is passed to tasks or async
methods, and the method should periodically check ‘token.IsCancellationRequested‘ or use ‘to-
ken.ThrowIfCancellationRequested()‘ to cancel the task.

Listing 11.85: Code example

1 // Example : Using CancellationToken to cancel a task


2 public async Task PerformCancellableTaskAsync ( CancellationToken token )

519
11.5. Advanced Concurrency and Multithreading

3 {
4 for ( int i = 0; i < 100; i ++)
5 {
6 token . ThrowIfCancellationRequested () ;
7 await Task . Delay (100) ; // Simulate work
8 }
9 }

What are continuations in the ‘Task‘ class, and how are they useful in
managing asynchronous workflows?
Continuations in the ‘Task‘ class allow you to specify actions that should run after a task completes.
This enables complex workflows where subsequent operations depend on the completion of one or
more preceding tasks, improving task chaining and management.

Listing 11.86: Code example

1 // Example : Adding a continuation to a Task


2 Task task = Task . Run (() = > Console . WriteLine ( " Initial Task " ) ) ;
3 task . ContinueWith ( t = > Console . WriteLine ( " Continuation Task " ) ) ;

How does the ‘TaskCompletionSource<T>‘ class work, and when would


you use it?
‘TaskCompletionSource<T>‘ is used to create a task that can be manually completed. It is
useful when integrating event-based or callback-based APIs into the ‘async/await‘ model, allowing
external sources to signal task completion.

Listing 11.87: Code example

1 // Example : No direct code example as preemptive multitasking is handled by the OS


in . NET .

What is the difference between cooperative and preemptive multitasking,


and which model does .NET use?
Cooperative multitasking requires threads to voluntarily yield control to allow other threads to
execute, whereas preemptive multitasking relies on the operating system to forcibly switch be-
tween threads. .NET uses preemptive multitasking, where the OS manages thread scheduling and
switching.

Listing 11.88: Code example

1 // Example : Using Parallel . ForEach for parallel data processing

520
11.5. Advanced Concurrency and Multithreading

2 Parallel . ForEach ( Enumerable . Range (1 , 100) , number = >


3 {
4 Console . WriteLine ( $ " Processing number { number } on thread { Thread . CurrentThread
. ManagedThreadId } " ) ;
5 }) ;

How does ‘Parallel.For‘ and ‘Parallel.ForEach‘ improve parallelism in


.NET applications?
‘Parallel.For‘ and ‘Parallel.ForEach‘ allow for parallel processing of data by distributing iterations
across multiple threads. These methods improve performance for CPU-bound tasks by utilizing
multiple cores for concurrent execution.

Listing 11.89: Code example

1 // Example : Using Task . Run for background work


2 public async Task < int > PerformComputationAsync ()
3 {
4 return await Task . Run (() = >
5 {
6 // Simulate CPU - bound work
7 return 42;
8 }) ;
9 }

What is the difference between ‘Task.Run‘ and ‘Task.Factory.StartNew‘,


and when should each be used?
‘Task.Run‘ is a simpler, high-level method for executing CPU-bound work in a background thread.
‘Task.Factory.StartNew‘ provides more control over task creation options such as task scheduling,
but is generally not recommended unless specific task settings are needed.

Listing 11.90: Code example

1 // Example : Using Task . Run for background work


2 public async Task < int > PerformComputationAsync ()
3 {
4 return await Task . Run (() = >
5 {
6 // Simulate CPU - bound work
7 return 42;
8 }) ;
9 }

521
11.5. Advanced Concurrency and Multithreading

What is ‘ThreadPool‘ in .NET, and why is it preferred over manually


creating threads?
‘ThreadPool‘ is a collection of reusable threads managed by the .NET runtime. It is preferred
over manually creating threads because it reduces the overhead of thread creation and destruction,
especially for short-lived tasks, by reusing threads.

Listing 11.91: Code example

1 // Example : Queueing work to the ThreadPool


2 ThreadPool . QueueUserWorkItem ( state = >
3 {
4 Console . WriteLine ( " Task executed by ThreadPool . " ) ;
5 }) ;

What is the ‘ConcurrentBag<T>‘, and when would you use it over other
thread-safe collections?
‘ConcurrentBag<T>‘ is a thread-safe collection designed for scenarios where multiple threads are
adding and removing items frequently. It is optimized for fast, unordered access and is especially
useful when the order of processing does not matter.

Listing 11.92: Code example

1 // Example : Using ConcurrentBag for thread - safe operations


2 ConcurrentBag < int > bag = new ConcurrentBag < int >() ;
3

4 Parallel . For (0 , 100 , i = >


5 {
6 bag . Add ( i ) ; // Add elements from multiple threads
7 }) ;

How does ‘Task.WaitAll‘ and ‘Task.WaitAny‘ work, and how are they
useful for managing multiple tasks?
‘Task.WaitAll‘ waits for all provided tasks to complete, while ‘Task.WaitAny‘ waits for the first
task to complete. These methods are useful for managing multiple asynchronous operations and
controlling execution flow based on task completion.

Listing 11.93: Code example

1 // Example : Using Task . WaitAll and Task . WaitAny


2 Task task1 = Task . Delay (1000) ;
3 Task task2 = Task . Delay (2000) ;
4

522
11.5. Advanced Concurrency and Multithreading

5 Task . WaitAll ( task1 , task2 ) ; // Wait for both tasks to complete


6 Task . WaitAny ( task1 , task2 ) ; // Wait for the first task to complete

How does the ‘Barrier‘ synchronization primitive help managing phased


execution in parallel tasks?
A ‘Barrier‘ allows multiple threads to work in phases, where each thread must complete its work
in one phase before any thread proceeds to the next. It is useful for coordinating threads that
must perform work in multiple stages.

Listing 11.94: Code example

1 // Example : Using Barrier for phased execution


2 Barrier barrier = new Barrier (3 , ( b ) = >
3 {
4 Console . WriteLine ( $ " Phase { b . CurrentPhaseNumber } completed . " ) ;
5 }) ;
6

7 public void DoPhasedWork ( int threadId )


8 {
9 Console . WriteLine ( $ " Thread { threadId } reached the barrier . " ) ;
10 barrier . SignalAndWait () ; // Wait for all threads to reach the barrier
11 Console . WriteLine ( $ " Thread { threadId } passed the barrier . " ) ;
12 }

How does the ‘Task.Yield‘ method affect task scheduling and performance
in a multithreaded environment?
‘Task.Yield‘ forces the current method to yield control back to the caller, resuming the method
asynchronously. This can be useful to prevent long-running tasks from monopolizing the thread
and improve responsiveness, particularly in UI applications.

Listing 11.95: Code example

1 // Example : Using Task . Yield to yield control


2 public async Task PerformLongRunningOperationAsync ()
3 {
4 await Task . Yield () ; // Yield control to the caller
5

6 for ( int i = 0; i < 100; i ++)


7 {
8 // Simulate work
9 }
10 }

523
11.5. Advanced Concurrency and Multithreading

How can you improve performance in ‘Parallel.ForEach‘ by controlling


the degree of parallelism?
You can control the degree of parallelism in ‘Parallel.ForEach‘ by specifying a ‘ParallelOptions‘
object, which allows you to limit the number of concurrent tasks. This is useful for preventing
resource exhaustion or managing CPU usage.

Listing 11.96: Code example

1 // Example : Controlling degree of parallelism in Parallel . ForEach


2 ParallelOptions parallelOptions = new ParallelOptions
3 {
4 MaxDegreeOfParallelism = 4 // Limit to 4 concurrent tasks
5 };
6

7 Parallel . ForEach ( Enumerable . Range (1 , 100) , parallelOptions , number = >


8 {
9 Console . WriteLine ( $ " Processing number { number } " ) ;
10 }) ;

What are ‘SemaphoreSlim‘ and ‘CountdownEvent‘, and how do they dif-


fer in their use cases?
‘SemaphoreSlim‘ limits the number of threads that can access a resource concurrently, while
‘CountdownEvent‘ is used to wait for a specific number of operations to complete before pro-
ceeding. ‘SemaphoreSlim‘ controls access to resources, while ‘CountdownEvent‘ manages task
coordination.

Listing 11.97: Code example

1 // Example : Using CountdownEvent for task coordination


2 CountdownEvent countdown = new CountdownEvent (3) ;
3

4 public void PerformWork ()


5 {
6 // Perform work
7 countdown . Signal () ; // Signal work is complete
8 }
9

10 public void WaitForCompletion ()


11 {
12 countdown . Wait () ; // Wait until all tasks complete
13 }

524
12 | Testing and Automation

Testing and automation are fundamental to delivering reliable and maintainable .NET applications.
Writing unit tests, integration tests, and automating test execution ensures that code behaves as
expected and that future changes do not introduce regressions. Frameworks like xUnit, NUnit,
and MSTest provide powerful tools to verify application logic systematically.

Employers increasingly expect candidates to demonstrate a testing mindset and familiarity with
test-driven development (TDD), mocking frameworks, and continuous integration workflows. In
interviews, questions often focus on how you ensure code quality, isolate dependencies for testing,
and incorporate automated testing into your development process.

Being proficient in testing and automation not only boosts your credibility as a developer but
also shows that you are committed to building stable, production-ready software. It reflects a
professional approach that reduces bugs, accelerates development cycles, and aligns with industry
best practices.

12.1 .NET Unit Testing, Techniques, Patterns, and Best Prac-


tices
What is unit testing, and why is it important in .NET?
Unit testing is the practice of writing tests that verify the behavior of individual methods or
components in isolation from the rest of the application. It is important because it ensures code
correctness, provides documentation, and facilitates refactoring and debugging without introducing
regressions.

Listing 12.1: Code example

1 public class Calculator


2 {
3 public int Add ( int a , int b )
4 {

525
12.1. .NET Unit Testing, Techniques, Patterns, and Best Practices

5 return a + b ;
6 }
7 }
8

9 // Unit Test using xUnit


10 public class CalculatorTests
11 {
12 [ Fact ]
13 public void Add_ReturnsCorrectSum ()
14 {
15 var calculator = new Calculator () ;
16 var result = calculator . Add (2 , 3) ;
17

18 Assert . Equal (5 , result ) ; // Assert that the result is correct


19 }
20 }

What are test doubles, and how are they used in unit testing?
Test doubles are objects that simulate the behavior of real objects in tests. They include mocks,
fakes, stubs, and spies, and are used to isolate the unit under test by replacing its dependencies
with controlled and predictable behaviors.
Listing 12.2: Code example

1 public interface IEmailService


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

6 public class EmailServiceFake : IEmailService


7 {
8 public bool EmailSent { get ; private set ; }
9

10 public void SendEmail ( string message )


11 {
12 EmailSent = true ; // Simulate sending email
13 }
14 }
15

16 // Unit Test with fake dependency


17 public class UserTests
18 {
19 [ Fact ]
20 public void RegisterUser_SendsWelcomeEmail ()
21 {
22 var emailService = new EmailServiceFake () ;

526
12.1. .NET Unit Testing, Techniques, Patterns, and Best Practices

23 var userService = new UserService ( emailService ) ;


24

25 userService . RegisterUser ( " test@example . com " ) ;


26

27 Assert . True ( emailService . EmailSent ) ; // Assert that the email was sent
28 }
29 }

How does dependency injection help in unit testing .NET applications?


Dependency injection (DI) makes it easier to test classes by injecting their dependencies from the
outside. This allows for better isolation and control over dependencies, as they can be replaced
with test doubles like mocks or fakes during testing.
Listing 12.3: Code example

1 public interface INotificationService


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

6 public class UserService


7 {
8 private readonly INotificationService _notificationService ;
9

10 public UserService ( INotificationService notificationService )


11 {
12 _notificationService = notificationService ;
13 }
14

15 public void RegisterUser ( string email )


16 {
17 // Registration logic ...
18 _notificationService . Notify ( $ " User registered with email : { email } " ) ;
19 }
20 }
21

22 // Unit Test using DI


23 public class UserServiceTests
24 {
25 [ Fact ]
26 public void RegisterUser_SendsNotification ()
27 {
28 var notificationServiceMock = new Mock < INotificationService >() ;
29 var userService = new UserService ( notificationServiceMock . Object ) ;
30

31 userService . RegisterUser ( " test@example . com " ) ;

527
12.1. .NET Unit Testing, Techniques, Patterns, and Best Practices

32

33 notificationServiceMock . Verify ( n = > n . Notify ( It . IsAny < string >() ) , Times .


Once ) ;
34 }
35 }

What is the Arrange-Act-Assert (AAA) pattern in unit testing?


The Arrange-Act-Assert (AAA) pattern is a common structure for writing unit tests. It involves
arranging the necessary objects and setting their state, acting by invoking the method under test,
and asserting the expected outcome.

Listing 12.4: Code example

1 public class CalculatorTests


2 {
3 [ Fact ]
4 public void Multiply_ReturnsCorrectProduct ()
5 {
6 // Arrange
7 var calculator = new Calculator () ;
8

9 // Act
10 var result = calculator . Multiply (2 , 3) ;
11

12 // Assert
13 Assert . Equal (6 , result ) ;
14 }
15 }

What is the difference between a mock and a stub in unit testing?


A mock is a test double that allows you to set up expectations about how it should be used during
the test and verify that these expectations are met. A stub, on the other hand, provides predefined
responses to calls but does not track or verify interactions.

Listing 12.5: Code example

1 public interface IDatabase


2 {
3 string GetData () ;
4 }
5

6 public class DatabaseStub : IDatabase


7 {
8 public string GetData ()

528
12.1. .NET Unit Testing, Techniques, Patterns, and Best Practices

9 {
10 return " Test data " ; // Stub returning predefined data
11 }
12 }
13

14 // Mock with Moq


15 public class DatabaseTests
16 {
17 [ Fact ]
18 public void GetData_CallsDatabase ()
19 {
20 var databaseMock = new Mock < IDatabase >() ;
21 databaseMock . Setup ( db = > db . GetData () ) . Returns ( " Test data " ) ;
22

23 var result = databaseMock . Object . GetData () ;


24

25 Assert . Equal ( " Test data " , result ) ; // Verify result


26 databaseMock . Verify ( db = > db . GetData () , Times . Once ) ; // Verify interaction
27 }
28 }

How does Test-Driven Development (TDD) improve code quality?


Test-Driven Development (TDD) is a practice where tests are written before the actual code. This
ensures that code is only written to meet the specific test requirements, leading to cleaner, more
focused, and better-tested code. It promotes continuous refactoring and code quality improvement.
Listing 12.6: Code example

1 // Example of TDD approach


2 public class CalculatorTests
3 {
4 [ Fact ]
5 public void Add_ReturnsCorrectSum ()
6 {
7 var calculator = new Calculator () ;
8 var result = calculator . Add (1 , 2) ;
9

10 Assert . Equal (3 , result ) ; // Write test before implementing Add method


11 }
12 }
13

14 public class Calculator


15 {
16 public int Add ( int a , int b )
17 {
18 return a + b ; // Implementation follows the test

529
12.1. .NET Unit Testing, Techniques, Patterns, and Best Practices

19 }
20 }

How do you test exceptions in .NET unit tests?


You can test exceptions in .NET unit tests using the ‘Assert.Throws‘ method to verify that a
specific exception is thrown when expected. This is useful for testing error handling in your code.

Listing 12.7: Code example

1 public class Calculator


2 {
3 public int Divide ( int a , int b )
4 {
5 if ( b == 0)
6 throw new DivideByZeroException () ;
7

8 return a / b ;
9 }
10 }
11

12 // Unit Test for exception


13 public class CalculatorTests
14 {
15 [ Fact ]
16 public void Divide_ByZero_ThrowsDivideByZeroException ()
17 {
18 var calculator = new Calculator () ;
19

20 Assert . Throws < DivideByZeroException >(() = > calculator . Divide (10 , 0) ) ;


21 }
22 }

What is the role of code coverage in unit testing, and how can you achieve
high coverage?
Code coverage measures the percentage of your code that is executed by unit tests. High coverage
can be achieved by writing tests for all branches, conditions, and paths in the code, ensuring that
both happy paths and edge cases are tested.

Listing 12.8: Code example

1 public class Calculator


2 {
3 public int Add ( int a , int b )
4 {

530
12.1. .NET Unit Testing, Techniques, Patterns, and Best Practices

5 return a + b ;
6 }
7

8 public int Subtract ( int a , int b )


9 {
10 return a - b ;
11 }
12 }
13

14 // Tests to achieve high coverage


15 public class CalculatorTests
16 {
17 [ Fact ]
18 public void Add_ReturnsCorrectSum ()
19 {
20 var calculator = new Calculator () ;
21 var result = calculator . Add (2 , 3) ;
22

23 Assert . Equal (5 , result ) ;


24 }
25

26 [ Fact ]
27 public void Subtract_ReturnsCorrectDifference ()
28 {
29 var calculator = new Calculator () ;
30 var result = calculator . Subtract (5 , 3) ;
31

32 Assert . Equal (2 , result ) ;


33 }
34 }

How do you test asynchronous methods in .NET?


You can test asynchronous methods in .NET by using ‘async‘ and ‘await‘ in your test methods.
The test framework, such as xUnit, should support asynchronous tests to allow assertions to be
made after awaiting the task.
Listing 12.9: Code example

1 public class AsyncService


2 {
3 public async Task < string > GetDataAsync ()
4 {
5 await Task . Delay (1000) ; // Simulate async work
6 return " Async Data " ;
7 }
8 }

531
12.1. .NET Unit Testing, Techniques, Patterns, and Best Practices

10 // Unit Test for async method


11 public class AsyncServiceTests
12 {
13 [ Fact ]
14 public async Task GetDataAsync_ReturnsData ()
15 {
16 var service = new AsyncService () ;
17

18 var result = await service . GetDataAsync () ;


19

20 Assert . Equal ( " Async Data " , result ) ;


21 }
22 }

What is parameterized testing, and how is it implemented in .NET?


Parameterized testing allows you to run the same test with different inputs, helping to reduce
duplicate test code. In xUnit, this is done using the ‘Theory‘ attribute along with ‘InlineData‘ for
different test cases.

Listing 12.10: Code example

1 public class Calculator


2 {
3 public int Add ( int a , int b )
4 {
5 return a + b ;
6 }
7 }
8

9 // Parameterized test using xUnit


10 public class CalculatorTests
11 {
12 [ Theory ]
13 [ InlineData (1 , 2 , 3) ]
14 [ InlineData (5 , 5 , 10) ]
15 [ InlineData (10 , 20 , 30) ]
16 public void Add_ReturnsCorrectSum ( int a , int b , int expected )
17 {
18 var calculator = new Calculator () ;
19 var result = calculator . Add (a , b ) ;
20

21 Assert . Equal ( expected , result ) ;


22 }
23 }

532
12.1. .NET Unit Testing, Techniques, Patterns, and Best Practices

How do you test private methods or properties in .NET?


Testing private methods directly is not recommended as unit tests should focus on public behavior.
However, you can test the public methods that call the private ones. Alternatively, reflection can
be used to access private members, but this should be avoided unless necessary.

Listing 12.11: Code example

1 public class UserService


2 {
3 private string HashPassword ( string password )
4 {
5 return Convert . ToBase64String ( Encoding . UTF8 . GetBytes ( password ) ) ;
6 }
7

8 public bool ValidatePassword ( string password , string hashedPassword )


9 {
10 return HashPassword ( password ) == hashedPassword ;
11 }
12 }
13

14 // Unit Test ( focus on public behavior )


15 public class UserServiceTests
16 {
17 [ Fact ]
18 public void ValidatePassword_CorrectPassword_ReturnsTrue ()
19 {
20 var service = new UserService () ;
21 var hashedPassword = service . ValidatePassword ( " password123 " , Convert .
ToBase64String ( Encoding . UTF8 . GetBytes ( " password123 " ) ) ) ;
22

23 Assert . True ( hashedPassword ) ;


24 }
25 }

How do you write unit tests for code with time-dependent logic?
For time-dependent logic, use abstractions such as injecting a ‘DateTimeProvider‘ instead of di-
rectly using ‘DateTime.Now‘. This allows you to mock or fake the time during testing.

Listing 12.12: Code example

1 public interface IDateTimeProvider


2 {
3 DateTime Now { get ; }
4 }
5

533
12.1. .NET Unit Testing, Techniques, Patterns, and Best Practices

6 public class UserService


7 {
8 private readonly IDateTimeProvider _dateTimeProvider ;
9

10 public UserService ( IDateTimeProvider dateTimeProvider )


11 {
12 _dateTimeProvider = dateTimeProvider ;
13 }
14

15 public bool IsPasswordExpired ( DateTime expirationDate )


16 {
17 return _dateTimeProvider . Now > expirationDate ;
18 }
19 }
20

21 // Unit Test with time - dependent logic


22 public class UserServiceTests
23 {
24 [ Fact ]
25 public void IsPasswordExpired_PasswordExpired_ReturnsTrue ()
26 {
27 var dateTimeProviderMock = new Mock < IDateTimeProvider >() ;
28 dateTimeProviderMock . Setup ( dp = > dp . Now ) . Returns ( new DateTime (2023 , 1 , 1) )
;
29

30 var service = new UserService ( dateTimeProviderMock . Object ) ;


31

32 var result = service . IsPasswordExpired ( new DateTime (2022 , 12 , 31) ) ;


33

34 Assert . True ( result ) ;


35 }
36 }

How do you use ‘Theory‘ and ‘InlineData‘ in xUnit for parameterized


unit testing?
In xUnit, the ‘Theory‘ attribute allows for parameterized testing, where different sets of data can
be passed to the same test method. ‘InlineData‘ is used to provide the data directly within the
test method definition.

Listing 12.13: Code example

1 public class MathTests


2 {
3 [ Theory ]
4 [ InlineData (2 , 3 , 5) ]

534
12.1. .NET Unit Testing, Techniques, Patterns, and Best Practices

5 [ InlineData (1 , 1 , 2) ]
6 [ InlineData (0 , 0 , 0) ]
7 public void Add_ReturnsCorrectSum ( int a , int b , int expected )
8 {
9 var result = a + b ;
10

11 Assert . Equal ( expected , result ) ;


12 }
13 }

How do you use mocking frameworks like Moq to mock dependencies in


.NET unit tests?
Moq is a popular mocking framework in .NET that allows you to create mock objects for depen-
dencies, set up expected behavior, and verify interactions. Moq is commonly used for isolating the
unit under test by replacing its dependencies.

Listing 12.14: Code example

1 public interface IEmailService


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

6 public class UserService


7 {
8 private readonly IEmailService _emailService ;
9

10 public UserService ( IEmailService emailService )


11 {
12 _emailService = emailService ;
13 }
14

15 public void RegisterUser ( string email )


16 {
17 // Registration logic ...
18 _emailService . SendEmail ( " Welcome ! " ) ;
19 }
20 }
21

22 // Unit Test with Moq


23 public class UserServiceTests
24 {
25 [ Fact ]
26 public void RegisterUser_SendsEmail ()
27 {

535
12.1. .NET Unit Testing, Techniques, Patterns, and Best Practices

28 var emailServiceMock = new Mock < IEmailService >() ;


29 var userService = new UserService ( emailServiceMock . Object ) ;
30

31 userService . RegisterUser ( " test@example . com " ) ;


32

33 emailServiceMock . Verify ( es = > es . SendEmail ( " Welcome ! " ) , Times . Once ) ;


34 }
35 }

How do you test logging in .NET applications?


You can test logging in .NET by injecting a mock or fake ‘ILogger‘ and verifying that the expected
log messages were written during the test. You can use mocking frameworks like Moq to verify
the log interactions.
Listing 12.15: Code example

1 public class Service


2 {
3 private readonly ILogger < Service > _logger ;
4

5 public Service ( ILogger < Service > logger )


6 {
7 _logger = logger ;
8 }
9

10 public void DoWork ()


11 {
12 _logger . LogInformation ( " Work started " ) ;
13 // Work logic ...
14 _logger . LogInformation ( " Work finished " ) ;
15 }
16 }
17

18 // Unit Test for logging


19 public class ServiceTests
20 {
21 [ Fact ]
22 public void DoWork_LogsInformation ()
23 {
24 var loggerMock = new Mock < ILogger < Service > >() ;
25 var service = new Service ( loggerMock . Object ) ;
26

27 service . DoWork () ;
28

29 loggerMock . Verify (
30 x = > x . LogInformation ( " Work started " ) ,

536
12.1. .NET Unit Testing, Techniques, Patterns, and Best Practices

31 Times . Once ) ;
32 loggerMock . Verify (
33 x = > x . LogInformation ( " Work finished " ) ,
34 Times . Once ) ;
35 }
36 }

How do you test HTTP requests and responses using ‘HttpClient‘ in


.NET?
You can test HTTP requests by mocking ‘HttpClient‘ using ‘HttpMessageHandler‘. This allows
you to simulate HTTP responses without making actual network calls during unit tests.

Listing 12.16: Code example

1 public class ApiService


2 {
3 private readonly HttpClient _httpClient ;
4

5 public ApiService ( HttpClient httpClient )


6 {
7 _httpClient = httpClient ;
8 }
9

10 public async Task < string > GetDataAsync ( string url )


11 {
12 var response = await _httpClient . GetAsync ( url ) ;
13 response . EnsureSuccessStatusCode () ;
14

15 return await response . Content . ReadAsStringAsync () ;


16 }
17 }
18

19 // Unit Test for HTTP request


20 public class ApiServiceTests
21 {
22 [ Fact ]
23 public async Task GetDataAsync_ReturnsData ()
24 {
25 var handlerMock = new Mock < HttpMessageHandler >() ;
26 handlerMock . Protected ()
27 . Setup < Task < HttpResponseMessage > >(
28 " SendAsync " ,
29 ItExpr . IsAny < HttpRequestMessage >() ,
30 ItExpr . IsAny < CancellationToken >() )
31 . ReturnsAsync ( new HttpResponseMessage

537
12.1. .NET Unit Testing, Techniques, Patterns, and Best Practices

32 {
33 StatusCode = HttpStatusCode . OK ,
34 Content = new StringContent ( " Test Data " )
35 }) ;
36

37 var httpClient = new HttpClient ( handlerMock . Object ) ;


38 var apiService = new ApiService ( httpClient ) ;
39

40 var result = await apiService . GetDataAsync ( " https :// example . com " ) ;
41

42 Assert . Equal ( " Test Data " , result ) ;


43 }
44 }

What is the role of ‘Assert.Equal‘ and ‘Assert.NotEqual‘ in unit testing?


‘Assert.Equal‘ verifies that two values are equal, while ‘Assert.NotEqual‘ verifies that two values
are not equal. These assertions are fundamental for verifying expected outcomes in unit tests.

Listing 12.17: Code example

1 public class CalculatorTests


2 {
3 [ Fact ]
4 public void Add_ReturnsCorrectSum ()
5 {
6 var calculator = new Calculator () ;
7 var result = calculator . Add (2 , 3) ;
8

9 Assert . Equal (5 , result ) ; // Asserts that the result is equal to 5


10 Assert . NotEqual (6 , result ) ; // Asserts that the result is not equal to 6
11 }
12 }

What is test isolation, and why is it important in unit testing?


Test isolation ensures that unit tests do not depend on each other or share state, allowing each
test to run independently. It is important because it prevents side effects from one test affecting
others, ensuring that tests are reliable and repeatable.

Listing 12.18: Code example

1 public class Counter


2 {
3 public int Value { get ; private set ; }
4

538
12.2. Practical Unit Test Examples, Code Coverage, and Tools

5 public void Increment () = > Value ++;


6 }
7

8 // Unit Tests with isolation


9 public class CounterTests
10 {
11 [ Fact ]
12 public void Increment_IncreasesValue ()
13 {
14 var counter = new Counter () ;
15 counter . Increment () ;
16

17 Assert . Equal (1 , counter . Value ) ; // This test is isolated and does not
depend on other tests
18 }
19

20 [ Fact ]
21 public void Increment_Twice_IncreasesValueTwice ()
22 {
23 var counter = new Counter () ;
24 counter . Increment () ;
25 counter . Increment () ;
26

27 Assert . Equal (2 , counter . Value ) ; // Each test runs in isolation


28 }
29 }

12.2 Practical Unit Test Examples, Code Coverage, and Tools


How do you test logic that depends on system time using dependency
injection?
For logic that depends on the system time, you can inject a time provider interface into the class
and mock or fake the current time in unit tests. This helps isolate the unit being tested and allows
you to control the system time during tests.

Listing 12.19: Code example

1 public interface ITimeProvider


2 {
3 DateTime Now { get ; }
4 }
5

6 public class TimeDependentService


7 {

539
12.2. Practical Unit Test Examples, Code Coverage, and Tools

8 private readonly ITimeProvider _timeProvider ;


9

10 public TimeDependentService ( ITimeProvider timeProvider )


11 {
12 _timeProvider = timeProvider ;
13 }
14

15 public bool IsBusinessHours ()


16 {
17 var currentTime = _timeProvider . Now ;
18 return currentTime . Hour >= 9 && currentTime . Hour <= 17;
19 }
20 }
21

22 // Unit test with mock time provider


23 public class TimeDependentServiceTests
24 {
25 [ Fact ]
26 public void IsBusinessHours_ReturnsTrue_WhenWithinBusinessHours ()
27 {
28 var timeProviderMock = new Mock < ITimeProvider >() ;
29 timeProviderMock . Setup ( tp = > tp . Now ) . Returns ( new DateTime (2023 , 1 , 1 , 10 ,
0 , 0) ) ;
30

31 var service = new TimeDependentService ( timeProviderMock . Object ) ;


32 var result = service . IsBusinessHours () ;
33

34 Assert . True ( result ) ; // 10 AM is within business hours


35 }
36 }

How do you test code that makes HTTP requests using ‘HttpClient‘ ?
You can mock ‘HttpClient‘ by mocking the ‘HttpMessageHandler‘ to simulate HTTP responses.
This allows you to test how your code handles HTTP requests and responses without making real
network calls.
Listing 12.20: Code example

1 public class ApiService


2 {
3 private readonly HttpClient _httpClient ;
4

5 public ApiService ( HttpClient httpClient )


6 {
7 _httpClient = httpClient ;
8 }

540
12.2. Practical Unit Test Examples, Code Coverage, and Tools

10 public async Task < string > FetchDataAsync ( string url )


11 {
12 var response = await _httpClient . GetAsync ( url ) ;
13 response . EnsureSuccessStatusCode () ;
14

15 return await response . Content . ReadAsStringAsync () ;


16 }
17 }
18

19 // Unit test with mock HttpClient


20 public class ApiServiceTests
21 {
22 [ Fact ]
23 public async Task FetchDataAsync_ReturnsData ()
24 {
25 var handlerMock = new Mock < HttpMessageHandler >() ;
26 handlerMock
27 . Protected ()
28 . Setup < Task < HttpResponseMessage > >(
29 " SendAsync " ,
30 ItExpr . IsAny < HttpRequestMessage >() ,
31 ItExpr . IsAny < CancellationToken >()
32 )
33 . ReturnsAsync ( new HttpResponseMessage
34 {
35 StatusCode = HttpStatusCode . OK ,
36 Content = new StringContent ( " Sample data " )
37 }) ;
38

39 var httpClient = new HttpClient ( handlerMock . Object ) ;


40 var apiService = new ApiService ( httpClient ) ;
41

42 var result = await apiService . FetchDataAsync ( " https :// example . com " ) ;
43

44 Assert . Equal ( " Sample data " , result ) ;


45 }
46 }

How do you ensure test coverage for exception handling in your code?
To ensure test coverage for exception handling, you should write unit tests that trigger exceptions
and verify that the correct exceptions are thrown using ‘Assert.Throws‘ or that the exceptions are
handled appropriately.

Listing 12.21: Code example

541
12.2. Practical Unit Test Examples, Code Coverage, and Tools

1 public class Calculator


2 {
3 public int Divide ( int a , int b )
4 {
5 if ( b == 0) throw new DivideByZeroException () ;
6 return a / b ;
7 }
8 }
9

10 // Unit test for exception handling


11 public class CalculatorTests
12 {
13 [ Fact ]
14 public void Divide_ThrowsDivideByZeroException_WhenDividingByZero ()
15 {
16 var calculator = new Calculator () ;
17

18 Assert . Throws < DivideByZeroException >(() = > calculator . Divide (10 , 0) ) ;


19 }
20 }

How do you achieve high code coverage when testing branching logic in
.NET?
To achieve high code coverage for branching logic, you should write unit tests that cover all branches
of the code, including ‘if‘, ‘else‘, ‘switch‘, and other conditional statements. Each path should be
tested to ensure correct behavior.

Listing 12.22: Code example

1 public class DiscountService


2 {
3 public decimal CalculateDiscount ( int customerYears )
4 {
5 if ( customerYears > 5)
6 {
7 return 0.20 m ; // 20% discount
8 }
9 else if ( customerYears > 1)
10 {
11 return 0.10 m ; // 10% discount
12 }
13 else
14 {
15 return 0.0 m ; // No discount
16 }

542
12.2. Practical Unit Test Examples, Code Coverage, and Tools

17 }
18 }
19

20 // Unit tests covering all branches


21 public class DiscountServiceTests
22 {
23 [ Fact ]
24 public void CalculateDiscount_Returns20Percent_WhenCustomerYearsGreaterThan5 ()
25 {
26 var service = new DiscountService () ;
27 var result = service . CalculateDiscount (6) ;
28

29 Assert . Equal (0.20 m , result ) ;


30 }
31

32 [ Fact ]
33 public void CalculateDiscount_Returns10Percent_WhenCustomerYearsGreaterThan1 ()
34 {
35 var service = new DiscountService () ;
36 var result = service . CalculateDiscount (2) ;
37

38 Assert . Equal (0.10 m , result ) ;


39 }
40

41 [ Fact ]
42 public void
CalculateDiscount_Returns0Percent_WhenCustomerYearsLessThanOrEqualTo1 ()
43 {
44 var service = new DiscountService () ;
45 var result = service . CalculateDiscount (1) ;
46

47 Assert . Equal (0.0 m , result ) ;


48 }
49 }

How do you write tests for methods with complex input data using ‘The-
ory‘ and ‘InlineData‘ in xUnit?
You can use the ‘Theory‘ attribute along with ‘InlineData‘ in xUnit to write parameterized tests.
This approach allows you to test methods with multiple sets of input data, reducing code dupli-
cation.

Listing 12.23: Code example

1 public class MathService


2 {

543
12.2. Practical Unit Test Examples, Code Coverage, and Tools

3 public int Multiply ( int a , int b )


4 {
5 return a * b ;
6 }
7 }
8

9 // Parameterized test using Theory and InlineData


10 public class MathServiceTests
11 {
12 [ Theory ]
13 [ InlineData (2 , 3 , 6) ]
14 [ InlineData (4 , 5 , 20) ]
15 [ InlineData (10 , 10 , 100) ]
16 public void Multiply_ReturnsCorrectProduct ( int a , int b , int expected )
17 {
18 var service = new MathService () ;
19 var result = service . Multiply (a , b ) ;
20

21 Assert . Equal ( expected , result ) ;


22 }
23 }

How do you mock external dependencies using Moq to ensure test isola-
tion?
Moq is a popular mocking framework that allows you to create mock objects for dependencies,
set up expectations, and verify interactions. You can use Moq to isolate external dependencies in
your unit tests.

Listing 12.24: Code example

1 public interface IEmailService


2 {
3 void SendEmail ( string recipient , string message ) ;
4 }
5

6 public class NotificationService


7 {
8 private readonly IEmailService _emailService ;
9

10 public NotificationService ( IEmailService emailService )


11 {
12 _emailService = emailService ;
13 }
14

15 public void NotifyUser ( string email )

544
12.2. Practical Unit Test Examples, Code Coverage, and Tools

16 {
17 _emailService . SendEmail ( email , " You have a new message ! " ) ;
18 }
19 }
20

21 // Unit test using Moq to mock dependency


22 public class NotificationServiceTests
23 {
24 [ Fact ]
25 public void NotifyUser_CallsSendEmailWithCorrectParameters ()
26 {
27 var emailServiceMock = new Mock < IEmailService >() ;
28 var notificationService = new NotificationService ( emailServiceMock . Object )
;
29

30 notificationService . NotifyUser ( " user@example . com " ) ;


31

32 emailServiceMock . Verify ( es = > es . SendEmail ( " user@example . com " , " You have a
new message ! " ) , Times . Once ) ;
33 }
34 }

How do you use code coverage tools like ‘coverlet‘ to measure code cov-
erage in .NET?
‘coverlet‘ is a cross-platform code coverage tool for .NET that integrates with xUnit and other test
frameworks. You can use it to measure the percentage of code that is executed during your tests,
helping to identify untested parts of the codebase.
Listing 12.25: Code example

1 // No code needed - coverlet is used with a command - line tool or build pipelines
2 // Example of running coverlet with xUnit :
3 // dotnet test / p : CollectCoverage = true / p : CoverletOutputFormat = lcov
4 public class CoverageExample
5 {
6 public int Add ( int a , int b ) = > a + b ;
7 }

How do you test void methods that perform actions but don’t return a
value?
For testing void methods, you can use mocks to verify that certain interactions or actions were
performed. You can use Moq to verify method calls or state changes without relying on a return
value.

545
12.2. Practical Unit Test Examples, Code Coverage, and Tools

Listing 12.26: Code example

1 public interface ILogService


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

6 public class ApplicationService


7 {
8 private readonly ILogService _logService ;
9

10 public ApplicationService ( ILogService logService )


11 {
12 _logService = logService ;
13 }
14

15 public void Run ()


16 {
17 _logService . Log ( " Application started " ) ;
18 }
19 }
20

21 // Unit test for void method


22 public class ApplicationServiceTests
23 {
24 [ Fact ]
25 public void Run_LogsApplicationStarted ()
26 {
27 var logServiceMock = new Mock < ILogService >() ;
28 var appService = new ApplicationService ( logServiceMock . Object ) ;
29

30 appService . Run () ;
31

32 logServiceMock . Verify ( ls = > ls . Log ( " Application started " ) , Times . Once ) ;
33 }
34 }

How do you test asynchronous methods that return a ‘Task‘ ?


To test asynchronous methods, you should use the ‘async‘ and ‘await‘ keywords in the test method.
Ensure the test framework supports async tests, and make assertions after awaiting the task.
Listing 12.27: Code example

1 public class AsyncService


2 {
3 public async Task < string > GetDataAsync ()
4 {

546
12.2. Practical Unit Test Examples, Code Coverage, and Tools

5 await Task . Delay (500) ;


6 return " Async Data " ;
7 }
8 }
9

10 // Unit test for async method


11 public class AsyncServiceTests
12 {
13 [ Fact ]
14 public async Task GetDataAsync_ReturnsCorrectData ()
15 {
16 var service = new AsyncService () ;
17 var result = await service . GetDataAsync () ;
18

19 Assert . Equal ( " Async Data " , result ) ;


20 }
21 }

How do you write tests for methods that rely on randomness or unpre-
dictable data?
To test methods that rely on randomness, abstract the random number generation logic into a
dependency and mock it in tests. This allows you to provide predictable random data during
tests.

Listing 12.28: Code example

1 public interface IRandomProvider


2 {
3 int GetRandomNumber ( int min , int max ) ;
4 }
5

6 public class LotteryService


7 {
8 private readonly IRandomProvider _randomProvider ;
9

10 public LotteryService ( IRandomProvider randomProvider )


11 {
12 _randomProvider = randomProvider ;
13 }
14

15 public int Draw ()


16 {
17 return _randomProvider . GetRandomNumber (1 , 100) ;
18 }
19 }

547
12.2. Practical Unit Test Examples, Code Coverage, and Tools

20

21 // Unit test for randomness


22 public class LotteryServiceTests
23 {
24 [ Fact ]
25 public void Draw_ReturnsFixedNumberInTest ()
26 {
27 var randomProviderMock = new Mock < IRandomProvider >() ;
28 randomProviderMock . Setup ( rp = > rp . GetRandomNumber (1 , 100) ) . Returns (42) ;
29

30 var lotteryService = new LotteryService ( randomProviderMock . Object ) ;


31 var result = lotteryService . Draw () ;
32

33 Assert . Equal (42 , result ) ; // The mocked random number is 42


34 }
35 }

How do you use ‘AutoFixture‘ to generate test data automatically?


‘AutoFixture‘ is a tool that generates anonymous test data for unit tests, reducing the need to
manually set up complex test objects. It can automatically populate objects with default or
customized values.

Listing 12.29: Code example

1 public class Customer


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

7 // Unit test with AutoFixture


8 public class CustomerTests
9 {
10 [ Fact ]
11 public void Customer_ShouldHaveNameAndAge ()
12 {
13 var fixture = new Fixture () ; // AutoFixture instance
14

15 var customer = fixture . Create < Customer >() ;


16

17 Assert . NotNull ( customer . Name ) ;


18 Assert . True ( customer . Age > 0) ;
19 }
20 }

548
12.2. Practical Unit Test Examples, Code Coverage, and Tools

How do you test methods that raise events in .NET?


To test methods that raise events, you can subscribe to the event in the test and assert that the
event was raised with the expected data.

Listing 12.30: Code example

1 public class Alarm


2 {
3 public event EventHandler AlarmRaised ;
4

5 public void Trigger ()


6 {
7 AlarmRaised ?. Invoke ( this , EventArgs . Empty ) ;
8 }
9 }
10

11 // Unit test for event - raising method


12 public class AlarmTests
13 {
14 [ Fact ]
15 public void Trigger_RaisesAlarmRaisedEvent ()
16 {
17 var alarm = new Alarm () ;
18 var eventRaised = false ;
19

20 alarm . AlarmRaised += ( sender , args ) = > eventRaised = true ;


21

22 alarm . Trigger () ;
23

24 Assert . True ( eventRaised ) ;


25 }
26 }

How do you achieve 100% code coverage with code that handles excep-
tions?
To achieve 100% code coverage for exception handling, write tests that trigger the exception
conditions and ensure that the catch blocks and exception-related code paths are executed and
tested.

Listing 12.31: Code example

1 public class FileService


2 {
3 public string ReadFile ( string path )
4 {

549
12.2. Practical Unit Test Examples, Code Coverage, and Tools

5 try
6 {
7 return File . ReadAllText ( path ) ;
8 }
9 catch ( FileNotFoundException )
10 {
11 return " File not found " ;
12 }
13 }
14 }
15

16 // Unit test for exception path


17 public class FileServiceTests
18 {
19 [ Fact ]
20 public void ReadFile_ReturnsErrorMessage_WhenFileNotFound ()
21 {
22 var service = new FileService () ;
23 var result = service . ReadFile ( " nonexistent . txt " ) ;
24

25 Assert . Equal ( " File not found " , result ) ;


26 }
27 }

How do you mock ‘DbContext‘ in unit tests for testing database interac-
tions?
You can mock ‘DbContext‘ in unit tests by using in-memory databases with ‘Entity Framework
Core‘’s ‘InMemory‘ provider, or by mocking the ‘DbSet‘ to simulate database operations without
a real database.

Listing 12.32: Code example

1 public class AppDbContext : DbContext


2 {
3 public DbSet < User > Users { get ; set ; }
4 }
5

6 public class UserService


7 {
8 private readonly AppDbContext _dbContext ;
9

10 public UserService ( AppDbContext dbContext )


11 {
12 _dbContext = dbContext ;
13 }

550
12.2. Practical Unit Test Examples, Code Coverage, and Tools

14

15 public User GetUserById ( int id )


16 {
17 return _dbContext . Users . Find ( id ) ;
18 }
19 }
20

21 // Unit test using in - memory database


22 public class UserServiceTests
23 {
24 [ Fact ]
25 public void GetUserById_ReturnsCorrectUser ()
26 {
27 var options = new DbContextOptionsBuilder < AppDbContext >()
28 . UseInMemoryDatabase ( databaseName : " TestDatabase " )
29 . Options ;
30

31 using var context = new AppDbContext ( options ) ;


32 context . Users . Add ( new User { Id = 1 , Name = " John " }) ;
33 context . SaveChanges () ;
34

35 var service = new UserService ( context ) ;


36 var user = service . GetUserById (1) ;
37

38 Assert . Equal ( " John " , user . Name ) ;


39 }
40 }

How do you use ‘TestDriven.NET‘ for continuous testing while coding?


‘TestDriven.NET‘ integrates testing into the development process by allowing you to run tests
directly from the code editor. It supports running unit tests continuously, helping you detect
issues early during development.

Listing 12.33: Code example

1 // No code required - TestDriven . NET is a tool that runs tests directly from the
editor
2 public class ContinuousTestingExample
3 {
4 public int Add ( int a , int b ) = > a + b ;
5 }

551
12.2. Practical Unit Test Examples, Code Coverage, and Tools

How do you test classes with multiple dependencies using constructor


injection?
When testing classes with multiple dependencies, you can mock each dependency using a mocking
framework like Moq and inject them into the class under test. This ensures that each dependency
is controlled and isolated during testing.

Listing 12.34: Code example

1 public interface IEmailService


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

6 public interface ILoggingService


7 {
8 void Log ( string message ) ;
9 }
10

11 public class OrderService


12 {
13 private readonly IEmailService _emailService ;
14 private readonly ILoggingService _loggingService ;
15

16 public OrderService ( IEmailService emailService , ILoggingService loggingService


)
17 {
18 _emailService = emailService ;
19 _loggingService = loggingService ;
20 }
21

22 public void PlaceOrder ()


23 {
24 _loggingService . Log ( " Order placed " ) ;
25 _emailService . SendEmail ( " Order confirmation " ) ;
26 }
27 }
28

29 // Unit test with multiple dependencies


30 public class OrderServiceTests
31 {
32 [ Fact ]
33 public void PlaceOrder_LogsAndSendsEmail ()
34 {
35 var emailServiceMock = new Mock < IEmailService >() ;
36 var loggingServiceMock = new Mock < ILoggingService >() ;
37

552
12.3. Advanced Test Automation and CI/CD

38 var orderService = new OrderService ( emailServiceMock . Object ,


loggingServiceMock . Object ) ;
39

40 orderService . PlaceOrder () ;
41

42 loggingServiceMock . Verify ( ls = > ls . Log ( " Order placed " ) , Times . Once ) ;
43 emailServiceMock . Verify ( es = > es . SendEmail ( " Order confirmation " ) , Times .
Once ) ;
44 }
45 }

12.3 Advanced Test Automation and CI/CD


What is test automation in the context of CI/CD, and why is it impor-
tant?
Test automation refers to the practice of using automated tools and scripts to execute test cases
in a CI/CD pipeline without manual intervention. It is important because it helps detect issues
early, speeds up the feedback cycle, and ensures that the application is continuously tested as it
evolves.

Listing 12.35: Code example

1 // Example : Simple automated test case using xUnit in a CI / CD pipeline


2 public class MathTests
3 {
4 [ Fact ]
5 public void Add_ReturnsCorrectSum ()
6 {
7 var result = Add (2 , 3) ;
8 Assert . Equal (5 , result ) ;
9 }
10

11 private int Add ( int a , int b ) = > a + b ;


12 }

How do you implement automated testing in a CI/CD pipeline using


GitLab CI?
In GitLab CI, automated testing can be implemented by defining test stages in the ‘.gitlab-ci.yml‘
file. These stages will run automatically whenever code is pushed or merged into a branch. The
pipeline ensures that the tests are run before code is deployed.

553
12.3. Advanced Test Automation and CI/CD

Listing 12.36: Code example

1 # . gitlab - ci . yml example for test automation


2 stages :
3 - build
4 - test
5

6 build_job :
7 stage : build
8 script :
9 - dotnet build
10

11 test_job :
12 stage : test
13 script :
14 - dotnet test -- logger " trx "

What is continuous testing, how does it integrate into CI/CD pipelines?


Continuous testing involves running automated tests at every stage of the software development
lifecycle, from code commit to deployment. It integrates into CI/CD pipelines by automatically
executing tests after every code change to ensure that new changes don’t break existing function-
ality.

Listing 12.37: Code example

1 # Continuous testing in a CI / CD pipeline ( Jenkins example )


2 pipeline {
3 agent any
4 stages {
5 stage ( ’ Build ’) {
6 steps {
7 sh ’ dotnet build ’
8 }
9 }
10 stage ( ’ Test ’) {
11 steps {
12 sh ’ dotnet test -- logger " trx " ’
13 }
14 }
15 }
16 }

554
12.3. Advanced Test Automation and CI/CD

How do you ensure flaky tests do not affect CI/CD pipelines?


Flaky tests, which fail intermittently, can disrupt CI/CD pipelines. To mitigate this, you can
implement strategies like retries, isolating flaky tests, marking them as unstable, or using parallel
testing environments to diagnose and fix flakiness.

Listing 12.38: Code example

1 // Example : Retrying a flaky test in xUnit using Polly retry policies


2 public class FlakyTests
3 {
4 [ Fact ]
5 public void FlakyTest_WithRetry ()
6 {
7 var policy = Policy
8 . Handle < Exception >()
9 . Retry (3) ;
10

11 policy . Execute (() = > RunFlakyTest () ) ;


12 }
13

14 private void RunFlakyTest ()


15 {
16 // Simulate a flaky test that occasionally fails
17 if ( new Random () . Next (1 , 3) == 1)
18 throw new Exception ( " Flaky test failed ! " ) ;
19 }
20 }

What role do test artifacts play in CI/CD pipelines, and how are they
used?
Test artifacts refer to the files or outputs generated during testing, such as test reports, logs, or
screenshots. In CI/CD pipelines, test artifacts are collected and stored to provide insights into the
test results and assist with debugging when failures occur.

Listing 12.39: Code example

1 # GitLab CI example : Storing test artifacts


2 test_job :
3 stage : test
4 script :
5 - dotnet test -- logger " trx "
6 artifacts :
7 paths :
8 - test - results /
9 when : always

555
12.3. Advanced Test Automation and CI/CD

How do you set up parallel test execution in a CI/CD pipeline to speed


up testing?
Parallel test execution allows you to split tests into multiple threads or containers that can run
concurrently, reducing overall test execution time. In a CI/CD pipeline, you can configure parallel
jobs or stages to achieve this.

Listing 12.40: Code example

1 # GitLab CI example : Running tests in parallel


2 test_job_1 :
3 stage : test
4 script :
5 - dotnet test -- filter " Category = FastTests "
6

7 test_job_2 :
8 stage : test
9 script :
10 - dotnet test -- filter " Category = SlowTests "
11

12 # Both jobs run in parallel

How do you integrate Selenium tests in a CI/CD pipeline?


Selenium tests can be integrated into a CI/CD pipeline by adding them as a stage in the pipeline.
The pipeline runs Selenium tests using browsers (either real or headless) to perform automated UI
testing, ensuring the application’s front-end behaves as expected.

Listing 12.41: Code example

1 # GitLab CI example : Running Selenium UI tests


2 selenium_test :
3 stage : test
4 image : " selenium / standalone - chrome "
5 script :
6 - dotnet test -- filter " Category = SeleniumTests "

How do you handle test dependencies in CI/CD pipelines?


Test dependencies, such as databases or services, must be set up and available during testing. You
can manage these dependencies in CI/CD pipelines by using Docker containers or initializing mock
services and databases before the tests run.

Listing 12.42: Code example

1 # GitLab CI example : Starting a database service for integration tests

556
12.3. Advanced Test Automation and CI/CD

2 services :
3 - postgres : latest
4

5 integration_tests :
6 stage : test
7 script :
8 - dotnet test -- filter " Category = IntegrationTests "

What is a test matrix in CI/CD, and how does it improve test coverage?
A test matrix is a configuration that allows you to run tests across different environments, such
as multiple operating systems, browser versions, or configurations. It improves test coverage by
ensuring that the application works correctly under various conditions.

Listing 12.43: Code example

1 # GitHub Actions example : Test matrix for running on multiple OS platforms


2 jobs :
3 test :
4 runs - on : $ {{ matrix . os }}
5 strategy :
6 matrix :
7 os : [ ubuntu - latest , windows - latest , macos - latest ]
8 steps :
9 - uses : actions / checkout@v2
10 - name : Install . NET
11 uses : actions / setup - dotnet@v1
12 - name : Run tests
13 run : dotnet test

How do you implement test retries in a CI/CD pipeline?


Test retries allow failed tests to be automatically re-executed in the pipeline to account for transient
failures. Most CI/CD systems allow you to configure retries for specific test jobs, which helps reduce
noise from intermittent failures.

Listing 12.44: Code example

1 # GitLab CI example : Configuring test retries


2 test_job :
3 stage : test
4 script :
5 - dotnet test
6 retry : 2 # Retry up to two times if the test fails

557
12.3. Advanced Test Automation and CI/CD

What is the purpose of code coverage thresholds in CI/CD pipelines?


Code coverage thresholds ensure that a certain percentage of the codebase is covered by tests. In
a CI/CD pipeline, you can enforce these thresholds, causing the pipeline to fail if coverage falls
below the specified threshold, thereby maintaining test quality.

Listing 12.45: Code example

1 # GitLab CI example : Enforcing code coverage thresholds


2 test_job :
3 stage : test
4 script :
5 - dotnet test -- collect : " Code Coverage "
6 coverage : ’/ Total .*?([0 -9]{1 ,3}) %/ ’
7 after_script :
8 - if [ " $CI_JOB_STATUS " == " success " ] && [ $COVERAGE - lt 80 ]; then exit 1;
fi

How do you implement static code analysis in a CI/CD pipeline?


Static code analysis checks the code for potential issues, such as bugs, vulnerabilities, and code
smells, without executing the code. Tools like ‘SonarQube‘ or ‘dotnet analyzers‘ can be integrated
into CI/CD pipelines to automatically scan code for issues during the build process.

Listing 12.46: Code example

1 # GitLab CI example : Integrating SonarQube for static code analysis


2 sonarqube_scan :
3 stage : test
4 script :
5 - sonar - scanner
6 only :
7 - master

How do you handle failed tests in a CI/CD pipeline without halting the
entire pipeline?
You can handle failed tests by marking certain jobs as "allow_failure" in CI/CD pipelines. This
ensures that the pipeline continues running even if some tests fail, allowing non-critical stages (e.g.,
deployment) to proceed while investigating failures.

Listing 12.47: Code example

1 # GitLab CI example : Allowing test failures without halting the pipeline


2 test_job :
3 stage : test

558
12.3. Advanced Test Automation and CI/CD

4 script :
5 - dotnet test
6 allow_failure : true

How do you implement integration tests with external services in CI/CD


pipelines?
Integration tests with external services require setting up those services (e.g., databases, APIs) in
the CI/CD environment. This can be done using Docker containers or mock services, ensuring
that the pipeline tests integration points without relying on production environments.

Listing 12.48: Code example

1 # GitHub Actions example : Running integration tests with external service


dependencies
2 jobs :
3 integration - tests :
4 runs - on : ubuntu - latest
5 services :
6 postgres :
7 image : postgres : latest
8 steps :
9 - run : dotnet test -- filter " Category = IntegrationTests "

How are feature flags used in test automation within CI/CD pipelines?
Feature flags allow you to enable or disable features at runtime without deploying new code. In test
automation, feature flags can be used to control which features are tested, allowing for controlled
rollouts and testing of features that are in development but not yet live.

Listing 12.49: Code example

1 // Example : Using a feature flag to control test logic


2 public class FeatureFlagService
3 {
4 public bool IsFeatureEnabled ( string featureName )
5 {
6 // Simulate feature flag logic ( could be external )
7 return featureName == " NewFeature " ;
8 }
9 }
10

11 // Unit test with feature flag


12 public class FeatureFlagTests
13 {
14 [ Fact ]

559
12.3. Advanced Test Automation and CI/CD

15 public void NewFeature_IsEnabled_ReturnsTrue ()


16 {
17 var service = new FeatureFlagService () ;
18 Assert . True ( service . IsFeatureEnabled ( " NewFeature " ) ) ;
19 }
20 }

How do you use ‘Azure Pipelines‘ for automated testing and deployment
in a CI/CD environment?
Azure Pipelines supports automated testing and deployment as part of the CI/CD process. You
can define pipeline steps in a YAML file that include stages for building, testing, and deploying
applications to various environments.

Listing 12.50: Code example

1 # Azure Pipelines example for automated testing


2 trigger :
3 - master
4

5 pool :
6 vmImage : ’ ubuntu - latest ’
7

8 steps :
9 - task : UseDotNet@2
10 inputs :
11 packageType : ’ sdk ’
12 version : ’ 5. x ’
13

14 - script : dotnet build


15 displayName : ’ Build ’
16

17 - script : dotnet test


18 displayName : ’ Test ’

How do you set up test reporting in CI/CD pipelines?


Test reporting in CI/CD pipelines involves generating and publishing test result reports, often in
formats like JUnit or HTML. These reports provide detailed insights into the test outcomes and
can be viewed in the CI/CD tool’s user interface.

Listing 12.51: Code example

1 # GitLab CI example : Generating and publishing test reports


2 test_job :
3 stage : test

560
12.3. Advanced Test Automation and CI/CD

4 script :
5 - dotnet test -- logger " trx ; LogFileName = test - results . trx "
6 artifacts :
7 paths :
8 - test - results . trx

How do you implement blue-green deployments in a CI/CD pipeline?


Blue-green deployments involve maintaining two production environments (blue and green) and
routing traffic to one while deploying updates to the other. Once the new environment (green) is
verified, traffic is switched from blue to green.

Listing 12.52: Code example

1 # GitLab CI example : Blue - Green deployment setup


2 deploy_blue :
3 stage : deploy
4 script :
5 - echo " Deploying to blue environment "
6

7 deploy_green :
8 stage : deploy
9 script :
10 - echo " Deploying to green environment "

How do you manage secrets (e.g., API keys) securely in CI/CD pipelines?
Secrets, such as API keys or passwords, should be stored securely using CI/CD platform secret
management features. Tools like GitHub Secrets, GitLab CI Variables, or Azure Key Vault allow
you to manage and inject secrets into the pipeline without exposing them in the code.

Listing 12.53: Code example

1 # GitLab CI example : Canary deployment setup


2 canary_deploy :
3 stage : deploy
4 script :
5 - echo " Deploying canary version to 10% of users "

How do you implement canary testing in a CI/CD pipeline?


Canary testing involves gradually rolling out a new version of an application to a small subset of
users before fully deploying it. In a CI/CD pipeline, canary testing can be managed by directing
a portion of traffic to the new version for monitoring and validation.

561
12.3. Advanced Test Automation and CI/CD

Listing 12.54: Code example

1 # GitLab CI example : Canary deployment setup


2 canary_deploy :
3 stage : deploy
4 script :
5 - echo " Deploying canary version to 10% of users "

562
13 | Web APIs in .Net

Web APIs are a cornerstone of modern .NET development, enabling applications to communicate
over HTTP and expose functionality to other systems, front-end clients, or third-party services.
ASP.NET Core provides a powerful, flexible framework for building RESTful APIs that are fast,
secure, and scalable.

In the context of job interviews, understanding how to design, implement, and secure Web APIs
is often a key requirement. Candidates may be asked to build endpoints, handle authentication
and authorization, manage request/response lifecycles, or demonstrate knowledge of versioning
and documentation tools like Swagger.

Mastering Web API development shows employers that you can build distributed systems, inte-
grate services, and support cross-platform communication. It highlights your ability to contribute
to real-world applications that rely on well-designed interfaces and reliable back-end infrastructure.

13.1 Web API REST Service Design With .NET Core


What is REST and how does it apply to web APIs in .NET Core?
REST (Representational State Transfer) is an architectural style that defines a set of constraints
for building scalable web services. RESTful services in .NET Core are typically designed using
HTTP methods like GET, POST, PUT, and DELETE to perform CRUD operations. They use
JSON or XML for data exchange and leverage stateless communication.

Listing 13.1: Code example

1 // Example : Basic REST API in . NET Core


2

3 [ ApiController ]
4 [ Route ( " api /[ controller ] " ) ]
5 public class ProductsController : ControllerBase
6 {
7 [ HttpGet ]

563
13.1. Web API REST Service Design With .NET Core

8 public IActionResult GetAllProducts ()


9 {
10 // Fetch all products logic
11 return Ok ( productList ) ;
12 }
13

14 [ HttpPost ]
15 public IActionResult CreateProduct ([ FromBody ] Product newProduct )
16 {
17 // Create new product logic
18 return CreatedAtAction ( nameof ( GetProductById ) , new { id = newProduct . Id } ,
newProduct ) ;
19 }
20 }

How do you implement versioning in a .NET Core Web API?


Versioning in .NET Core Web API can be implemented by using attributes to define different API
versions. You can use query string parameters, HTTP headers, or the URL path to specify the API
version. The ‘Microsoft.AspNetCore.Mvc.Versioning‘ package simplifies the versioning process.
Listing 13.2: Code example

1 // Example : API versioning using URL path


2

3 [ ApiController ]
4 [ Route ( " api / v { version : apiVersion }/[ controller ] " ) ]
5 [ ApiVersion ( " 1.0 " ) ]
6 [ ApiVersion ( " 2.0 " ) ]
7 public class ProductsController : ControllerBase
8 {
9 [ HttpGet ]
10 [ MapToApiVersion ( " 1.0 " ) ]
11 public IActionResult GetAllProductsV1 ()
12 {
13 // Version 1 logic
14 return Ok ( productList ) ;
15 }
16

17 [ HttpGet ]
18 [ MapToApiVersion ( " 2.0 " ) ]
19 public IActionResult GetAllProductsV2 ()
20 {
21 // Version 2 logic
22 return Ok ( productListV2 ) ;
23 }
24 }

564
13.1. Web API REST Service Design With .NET Core

How do you secure a Web API in .NET Core using JWT (JSON Web
Tokens)?
Securing a Web API in .NET Core using JWT involves issuing and validating tokens. JWT tokens
are created during user authentication and passed in the HTTP headers to verify the user’s identity
for subsequent requests.

Listing 13.3: Code example

1 // Example : Securing Web API with JWT


2

3 public void ConfigureServices ( IServiceCollection services )


4 {
5 services . AddAuthentication ( JwtBearerDefaults . AuthenticationScheme )
6 . AddJwtBearer ( options = >
7 {
8 options . TokenValidationParameters = new TokenValidationParameters
9 {
10 ValidateIssuer = true ,
11 ValidateAudience = true ,
12 ValidateLifetime = true ,
13 ValidateIssuerSigningKey = true ,
14 ValidIssuer = " yourdomain . com " ,
15 ValidAudience = " yourdomain . com " ,
16 IssuerSigningKey = new SymmetricSecurityKey ( Encoding . UTF8 .
GetBytes ( " YourSecretKey " ) )
17 };
18 }) ;
19 }
20

21 [ Authorize ]
22 [ HttpGet ]
23 public IActionResult GetSecureData ()
24 {
25 return Ok ( " This is a secured endpoint " ) ;
26 }

How do you implement global exception handling in a .NET Core Web


API?
Global exception handling can be implemented in a .NET Core Web API using middleware or
by creating a custom exception filter. This allows for catching unhandled exceptions globally and
returning appropriate error responses.

Listing 13.4: Code example

565
13.1. Web API REST Service Design With .NET Core

1 // Example : Global exception handling using middleware


2

3 public class ExceptionMiddleware


4 {
5 private readonly RequestDelegate _next ;
6 private readonly ILogger < ExceptionMiddleware > _logger ;
7

8 public ExceptionMiddleware ( RequestDelegate next , ILogger < ExceptionMiddleware >


logger )
9 {
10 _next = next ;
11 _logger = logger ;
12 }
13

14 public async Task InvokeAsync ( HttpContext httpContext )


15 {
16 try
17 {
18 await _next ( httpContext ) ;
19 }
20 catch ( Exception ex )
21 {
22 _logger . LogError ( $ " Something went wrong : { ex } " ) ;
23 await HandleExceptionAsync ( httpContext , ex ) ;
24 }
25 }
26

27 private Task HandleExceptionAsync ( HttpContext context , Exception exception )


28 {
29 context . Response . ContentType = " application / json " ;
30 context . Response . StatusCode = ( int ) HttpStatusCode . InternalServerError ;
31 return context . Response . WriteAsync ( new ErrorDetails ()
32 {
33 StatusCode = context . Response . StatusCode ,
34 Message = " Internal Server Error from middleware . "
35 }. ToString () ) ;
36 }
37 }

How do you implement pagination in a .NET Core Web API?


Pagination in a .NET Core Web API can be implemented by accepting query parameters like page
number and page size in your controller actions. You then apply these parameters to your data
retrieval logic to return a subset of results.

Listing 13.5: Code example

566
13.1. Web API REST Service Design With .NET Core

1 // Example : Implementing pagination in a Web API


2

3 [ HttpGet ]
4 public IActionResult GetPagedProducts ( int pageNumber = 1 , int pageSize = 10)
5 {
6 var pagedProducts = productList . Skip (( pageNumber - 1) * pageSize ) . Take (
pageSize ) . ToList () ;
7 return Ok ( pagedProducts ) ;
8 }

How do you implement a file upload API in .NET Core?


To implement a file upload API in .NET Core, you can use the ‘IFormFile‘ interface to handle
incoming file uploads. The file can then be saved to a specified location on the server.

Listing 13.6: Code example

1 // Example : File upload API


2

3 [ HttpPost ( " upload " ) ]


4 public async Task < IActionResult > UploadFile ( IFormFile file )
5 {
6 if ( file == null || file . Length == 0)
7 {
8 return BadRequest ( " No file uploaded . " ) ;
9 }
10

11 var filePath = Path . Combine ( Directory . GetCurrentDirectory () , " uploads " , file .
FileName ) ;
12

13 using ( var stream = new FileStream ( filePath , FileMode . Create ) )


14 {
15 await file . CopyToAsync ( stream ) ;
16 }
17

18 return Ok ( new { filePath }) ;


19 }

How do you implement response caching in a .NET Core Web API?


Response caching in a .NET Core Web API can be implemented using the ‘[ResponseCache]‘
attribute. This attribute controls the HTTP caching headers that dictate how clients should cache
responses.

Listing 13.7: Code example

567
13.1. Web API REST Service Design With .NET Core

1 // Example : Caching responses for a Web API


2

3 [ HttpGet ]
4 [ ResponseCache ( Duration = 60 , Location = ResponseCacheLocation . Client ) ]
5 public IActionResult GetProducts ()
6 {
7 return Ok ( productList ) ;
8 }

How do you implement dependency injection in a .NET Core Web API?


Dependency injection in a .NET Core Web API is implemented using the built-in dependency
injection (DI) container. Services and repositories are registered in the ‘Startup.ConfigureServices‘
method and injected into controllers.

Listing 13.8: Code example

1 // Example : Dependency injection in a Web API


2

3 public class ProductsController : ControllerBase


4 {
5 private readonly IProductService _productService ;
6

7 public ProductsController ( IProductService productService )


8 {
9 _productService = productService ;
10 }
11

12 [ HttpGet ]
13 public IActionResult GetProducts ()
14 {
15 return Ok ( _productService . GetAllProducts () ) ;
16 }
17 }

How do you handle route constraints in a .NET Core Web API?


Route constraints in .NET Core Web APIs are used to restrict the matching of routes based on
conditions like data types or values. Constraints can be applied directly to route templates.

Listing 13.9: Code example

1 // Example : Route constraint for integer ID


2

3 [ HttpGet ( " { id : int } " ) ]


4 public IActionResult GetProductById ( int id )

568
13.1. Web API REST Service Design With .NET Core

5 {
6 var product = _productService . GetProductById ( id ) ;
7 if ( product == null )
8 {
9 return NotFound () ;
10 }
11 return Ok ( product ) ;
12 }

How do you use Swagger for API documentation in .NET Core Web API?
Swagger is used to automatically generate API documentation and interactive testing interfaces
for your Web API. In .NET Core, you can use the ‘Swashbuckle.AspNetCore‘ package to integrate
Swagger.

Listing 13.10: Code example

1 // Example : Adding Swagger to a Web API


2

3 public void ConfigureServices ( IServiceCollection services )


4 {
5 services . AddSwaggerGen () ;
6 }
7

8 public void Configure ( IApplicationBuilder app , IHostingEnvironment env )


9 {
10 app . UseSwagger () ;
11 app . UseSwaggerUI ( c = >
12 {
13 c . SwaggerEndpoint ( " / swagger / v1 / swagger . json " , " My API V1 " ) ;
14 }) ;
15 }

How do you implement custom middleware in .NET Core to log requests


and responses?
Custom middleware can be implemented in .NET Core by creating a middleware class that pro-
cesses incoming HTTP requests and outgoing HTTP responses. Middleware can be used to log
request/response data or apply custom logic globally.

Listing 13.11: Code example

1 // Example : Custom logging middleware


2

3 public class RequestResponseLoggingMiddleware


4 {

569
13.1. Web API REST Service Design With .NET Core

5 private readonly RequestDelegate _next ;


6 private readonly ILogger < RequestResponseLoggingMiddleware > _logger ;
7

8 public RequestResponseLoggingMiddleware ( RequestDelegate next , ILogger <


RequestResponseLoggingMiddleware > logger )
9 {
10 _next = next ;
11 _logger = logger ;
12 }
13

14 public async Task Invoke ( HttpContext context )


15 {
16 _logger . LogInformation ( $ " Handling request : { context . Request . Method } {
context . Request . Path } " ) ;
17

18 await _next ( context ) ;


19

20 _logger . LogInformation ( $ " Response : { context . Response . StatusCode } " ) ;


21 }
22 }

How do you validate incoming request data in a .NET Core Web API?
Data validation in .NET Core Web API can be handled using model validation attributes like
‘[Required]‘, ‘[StringLength]‘, and ‘[Range]‘. Validation attributes can be applied to models, and
invalid data automatically returns a ‘400 Bad Request‘ response.
Listing 13.12: Code example

1 // Example : Data validation in a Web API


2

3 public class Product


4 {
5 [ Required ]
6 public string Name { get ; set ; }
7

8 [ Range (0.01 , 1000.00) ]


9 public decimal Price { get ; set ; }
10 }
11

12 [ HttpPost ]
13 public IActionResult CreateProduct ([ FromBody ] Product product )
14 {
15 if (! ModelState . IsValid )
16 {
17 return BadRequest ( ModelState ) ;
18 }

570
13.1. Web API REST Service Design With .NET Core

19

20 // Create product logic


21 return Ok ( product ) ;
22 }

How do you implement async/await in a .NET Core Web API?


Async/await is used in .NET Core Web API to perform asynchronous operations like database
queries or HTTP calls without blocking the thread. It allows the API to handle more requests
concurrently.

Listing 13.13: Code example

1 // Example : Async / await in a Web API


2

3 [ HttpGet ]
4 public async Task < IActionResult > GetProductsAsync ()
5 {
6 var products = await _productService . GetProductsAsync () ;
7 return Ok ( products ) ;
8 }

How do you ensure idempotency for PUT and DELETE methods in a


RESTful API with ?
Idempotency in a RESTful API ensures that multiple identical requests result in the same state
change. For PUT and DELETE methods, this means ensuring that the state remains unchanged
if the same request is repeated.

Listing 13.14: Code example

1 // Example : Idempotent PUT method


2

3 [ HttpPut ( " { id } " ) ]


4 public IActionResult UpdateProduct ( int id , [ FromBody ] Product product )
5 {
6 var existingProduct = _productService . GetProductById ( id ) ;
7 if ( existingProduct == null )
8 {
9 return NotFound () ;
10 }
11

12 _productService . UpdateProduct ( id , product ) ;


13 return NoContent () ;
14 }

571
13.2. ASP.Net WebAPI Routing, Endpoints, Controllers, DI, Model Binding, Validations,
Versioning

How do you implement HATEOAS (Hypermedia as the Engine of Appli-


cation State) in a .NET Core Web API?
HATEOAS in a RESTful API provides navigational links with the response to guide clients on
possible actions. You can implement it by including related resource links in the API response.

Listing 13.15: Code example

1 // Example : Implementing HATEOAS in a Web API response


2

3 [ HttpGet ( " { id } " ) ]


4 public IActionResult GetProductById ( int id )
5 {
6 var product = _productService . GetProductById ( id ) ;
7 if ( product == null )
8 {
9 return NotFound () ;
10 }
11

12 var links = new List < Link >


13 {
14 new Link { Rel = " self " , Href = $ " / api / products /{ id } " } ,
15 new Link { Rel = " update " , Href = $ " / api / products /{ id } " , Method = " PUT " } ,
16 new Link { Rel = " delete " , Href = $ " / api / products /{ id } " , Method = " DELETE "
}
17 };
18

19 var resource = new Resource < Product >( product , links ) ;


20 return Ok ( resource ) ;
21 }

13.2 ASP.Net WebAPI Routing, Endpoints, Controllers, DI,


Model Binding, Validations, Versioning
How does attribute-based routing work in ASP.NET Core WebAPI?
Attribute-based routing allows you to define routes directly on controllers and action methods
using attributes like ‘[Route]‘, ‘[HttpGet]‘, ‘[HttpPost]‘, etc. This provides better control and
clarity over the routing configuration.

Listing 13.16: Code example

1 // Attribute - based routing example


2 [ ApiController ]
3 [ Route ( " api /[ controller ] " ) ]

572
13.2. ASP.Net WebAPI Routing, Endpoints, Controllers, DI, Model Binding, Validations,
Versioning

4 public class ProductsController : ControllerBase


5 {
6 [ HttpGet ( " { id } " ) ]
7 public IActionResult GetProductById ( int id )
8 {
9 // Fetch the product by ID
10 return Ok ( new { ProductId = id , Name = " Sample Product " }) ;
11 }
12

13 [ HttpPost ]
14 public IActionResult CreateProduct ([ FromBody ] Product product )
15 {
16 // Create a new product
17 return CreatedAtAction ( nameof ( GetProductById ) , new { id = product . Id } ,
product ) ;
18 }
19 }

How do you configure conventional routing in ASP.NET Core WebAPI?


Conventional routing is defined globally in the ‘Startup.Configure‘ method using ‘UseEndpoints‘.
It maps URL patterns to controllers and actions based on predefined templates.

Listing 13.17: Code example

1 public void Configure ( IApplicationBuilder app )


2 {
3 app . UseRouting () ;
4

5 app . UseEndpoints ( endpoints = >


6 {
7 endpoints . MapControllerRoute (
8 name : " default " ,
9 pattern : " { controller = Home }/{ action = Index }/{ id ?} " ) ;
10 }) ;
11 }

What is endpoint routing, and how does it differ from traditional routing
in ASP.NET Core?
Endpoint routing unifies middleware and MVC routing. It allows middleware to select an endpoint
and execute it based on incoming requests, providing a more integrated routing system.

Listing 13.18: Code example

1 public void Configure ( IApplicationBuilder app )

573
13.2. ASP.Net WebAPI Routing, Endpoints, Controllers, DI, Model Binding, Validations,
Versioning

2 {
3 app . UseRouting () ;
4

5 app . UseEndpoints ( endpoints = >


6 {
7 endpoints . MapGet ( " / api / health " , async context = >
8 {
9 await context . Response . WriteAsync ( " API is healthy " ) ;
10 }) ;
11 }) ;
12 }

How can you define route constraints in ASP.NET Core WebAPI?


Route constraints restrict the acceptable values for route parameters using predefined or custom
constraints. For example, ‘int‘, ‘guid‘, or regex constraints.

Listing 13.19: Code example

1 [ HttpGet ( " { id : int } " ) ] // Constraint : ID must be an integer


2 public IActionResult GetProductById ( int id )
3 {
4 return Ok ( new { ProductId = id }) ;
5 }
6

7 [ HttpGet ( " { id : guid } " ) ] // Constraint : ID must be a GUID


8 public IActionResult GetProductByGuid ( Guid id )
9 {
10 return Ok ( new { ProductId = id }) ;
11 }

How do you implement dependency injection (DI) in ASP.NET Core


WebAPI?
ASP.NET Core provides a built-in DI container to register and resolve dependencies. Services
are registered in ‘Startup.ConfigureServices‘ using lifetime scopes such as Singleton, Scoped, or
Transient.

Scopes:
• Transient: Instance Creation: Every time requested. Typical Use Case: Stateless or
lightweight services
• Scoped: Instance Creation: Once per HTTP request. Typical Use Case: Request-specific
services like DbContext

574
13.2. ASP.Net WebAPI Routing, Endpoints, Controllers, DI, Model Binding, Validations,
Versioning

• Singleton: Instance Creation: Once per application lifetime. Typical Use Case: Shared
state or expensive-to-create services

Listing 13.20: Code example

1 public interface IProductService


2 {
3 IEnumerable < Product > GetAllProducts () ;
4 }
5

6 public class ProductService : IProductService


7 {
8 public IEnumerable < Product > GetAllProducts ()
9 {
10 return new List < Product > { new Product { Id = 1 , Name = " Sample Product " }
};
11 }
12 }
13

14 // Registering the service


15 public void ConfigureServices ( IServiceCollection services )
16 {
17 services . AddScoped < IProductService , ProductService >() ;
18 }
19

20 // Using the service in a controller


21 [ ApiController ]
22 [ Route ( " api /[ controller ] " ) ]
23 public class ProductsController : ControllerBase
24 {
25 private readonly IProductService _productService ;
26

27 public ProductsController ( IProductService productService )


28 {
29 _productService = productService ;
30 }
31

32 [ HttpGet ]
33 public IActionResult GetProducts ()
34 {
35 return Ok ( _productService . GetAllProducts () ) ;
36 }
37 }

575
13.2. ASP.Net WebAPI Routing, Endpoints, Controllers, DI, Model Binding, Validations,
Versioning

How does model binding work in ASP.NET Core WebAPI?


Model binding automatically maps HTTP request data to action method parameters or model
objects. It works with data from the query string, form data, route values, or JSON in the request
body.

Listing 13.21: Code example

1 [ HttpPost ]
2 public IActionResult CreateProduct ([ FromBody ] Product product )
3 {
4 // Model binding maps JSON in the request body to the Product object
5 if ( ModelState . IsValid )
6 {
7 return Ok ( product ) ;
8 }
9 return BadRequest ( ModelState ) ;
10 }

What is model validation, and how do you enforce it in WebAPI?


Model validation ensures that incoming data adheres to defined validation rules. Validation at-
tributes like ‘[Required]‘, ‘[StringLength]‘, and ‘[Range]‘ are used in model classes. ASP.NET Core
automatically validates models and populates ‘ModelState‘.

Listing 13.22: Code example

1 public class Product


2 {
3 [ Required ]
4 public string Name { get ; set ; }
5

6 [ Range (0.01 , 1000.00) ]


7 public decimal Price { get ; set ; }
8 }
9

10 [ HttpPost ]
11 public IActionResult CreateProduct ([ FromBody ] Product product )
12 {
13 if (! ModelState . IsValid )
14 {
15 return BadRequest ( ModelState ) ;
16 }
17 return Ok ( product ) ;
18 }

576
13.2. ASP.Net WebAPI Routing, Endpoints, Controllers, DI, Model Binding, Validations,
Versioning

How do you implement API versioning in ASP.NET Core WebAPI?


ASP.NET Core supports API versioning using the ‘Microsoft.AspNetCore.Mvc.Versioning‘ pack-
age. Versioning can be implemented using query strings, headers, or URL segments.

Listing 13.23: Code example

1 // Configuring versioning in Startup . cs


2 public void ConfigureServices ( IServiceCollection services )
3 {
4 services . AddApiVersioning ( options = >
5 {
6 options . AssumeDefaultVersionWhenUnspecified = true ;
7 options . DefaultApiVersion = new ApiVersion (1 , 0) ;
8 options . ReportApiVersions = true ;
9 }) ;
10 }
11

12 // Versioned controller
13 [ ApiController ]
14 [ Route ( " api / v { version : apiVersion }/ products " ) ]
15 [ ApiVersion ( " 1.0 " ) ]
16 [ ApiVersion ( " 2.0 " ) ]
17 public class ProductsController : ControllerBase
18 {
19 [ HttpGet ]
20 public IActionResult GetV1 () = > Ok ( " Products from API v1 .0 " ) ;
21

22 [ HttpGet , MapToApiVersion ( " 2.0 " ) ]


23 public IActionResult GetV2 () = > Ok ( " Products from API v2 .0 " ) ;
24 }

How do you handle validation errors in ASP.NET Core WebAPI?


Validation errors are automatically added to ‘ModelState‘. Use the ‘ModelState‘ object to return
validation errors to the client in a standardized format.

Listing 13.24: Code example

1 [ HttpPost ]
2 public IActionResult CreateProduct ([ FromBody ] Product product )
3 {
4 if (! ModelState . IsValid )
5 {
6 return BadRequest ( new
7 {
8 ErrorMessage = " Validation Failed " ,

577
13.2. ASP.Net WebAPI Routing, Endpoints, Controllers, DI, Model Binding, Validations,
Versioning

9 Errors = ModelState . Values . SelectMany ( v = > v . Errors . Select ( e = > e .


ErrorMessage ) )
10 }) ;
11 }
12 return Ok ( product ) ;
13 }

How does ‘[FromServices]‘ work in ASP.NET Core WebAPI?


The ‘[FromServices]‘ attribute allows injecting services directly into action methods instead of the
controller constructor.

Listing 13.25: Code example

1 [ HttpGet ]
2 public IActionResult GetProducts ([ FromServices ] IProductService productService )
3 {
4 return Ok ( productService . GetAllProducts () ) ;
5 }

What are ‘[Bind]‘ attributes in ASP.NET Core WebAPI?


The ‘[Bind]‘ attribute restricts the properties that model binding updates, allowing developers to
prevent over-posting attacks.

Listing 13.26: Code example

1 [ HttpPost ]
2 public IActionResult UpdateProduct ([ Bind ( " Name , Price " ) ] Product product )
3 {
4 // Only Name and Price properties will be bound from the incoming request
5 return Ok ( product ) ;
6 }

How does ‘[Consumes]‘ help specify request formats in ASP.NET Core


WebAPI?
The ‘[Consumes]‘ attribute specifies the expected request content type (e.g., ‘application/json‘) for
an action method, ensuring compatibility and validation of incoming requests.

578
13.2. ASP.Net WebAPI Routing, Endpoints, Controllers, DI, Model Binding, Validations,
Versioning

How does ‘[Produces]‘ help specify response formats in ASP.NET Core


WebAPI?
The ‘[Produces]‘ attribute specifies the content types a controller action can produce. This is useful
for setting consistent response formats (e.g., ‘application/json‘, ‘application/xml‘) for clients.

Listing 13.27: Code example

1 [ Produces ( " application / json " ) ]


2 [ ApiController ]
3 [ Route ( " api /[ controller ] " ) ]
4 public class ProductsController : ControllerBase
5 {
6 [ HttpGet ]
7 public IActionResult GetProducts ()
8 {
9 return Ok ( new List < Product > { new Product { Id = 1 , Name = " Sample " } }) ;
10 }
11 }

How do you create a custom route attribute in ASP.NET Core WebAPI?


A custom route attribute can be created by inheriting from ‘RouteAttribute‘ and defining custom
behavior.

Listing 13.28: Code example

1 public class CustomRouteAttribute : RouteAttribute


2 {
3 public CustomRouteAttribute ( string template ) : base ( $ " custom /{ template } " )
4 {
5 }
6 }
7

8 // Using the custom route


9 [ ApiController ]
10 [ CustomRoute ( " products " ) ]
11 public class ProductsController : ControllerBase
12 {
13 [ HttpGet ]
14 public IActionResult GetProducts ()
15 {
16 return Ok ( new { Message = " Custom route works ! " }) ;
17 }
18 }

579
13.2. ASP.Net WebAPI Routing, Endpoints, Controllers, DI, Model Binding, Validations,
Versioning

How can you implement hierarchical routing in ASP.NET Core WebAPI?


Hierarchical routing can be implemented using attribute routing with child routes.

Listing 13.29: Code example

1 [ Route ( " api /[ controller ] " ) ]


2 [ ApiController ]
3 public class ProductsController : ControllerBase
4 {
5 [ HttpGet ]
6 public IActionResult GetAll () = > Ok ( " All Products " ) ;
7

8 [ HttpGet ( " { productId }/ details " ) ]


9 public IActionResult GetDetails ( int productId ) = > Ok ( $ " Details for Product {
productId } " ) ;
10 }

How do you enforce HTTPS in an ASP.NET Core WebAPI application?


You can enforce HTTPS by using the ‘UseHttpsRedirection‘ middleware in ‘Startup.Configure‘.

Listing 13.30: Code example

1 public void Configure ( IApplicationBuilder app )


2 {
3 app . UseHttpsRedirection () ;
4 app . UseRouting () ;
5 app . UseEndpoints ( endpoints = > { endpoints . MapControllers () ; }) ;
6 }

What is the difference between ‘[FromQuery]‘, ‘[FromRoute]‘, and ‘[From-


Body]‘ in ASP.NET Core WebAPI?
- ‘[FromQuery]‘: Binds data from the query string. - ‘[FromRoute]‘: Binds data from the route
parameters. - ‘[FromBody]‘: Binds data from the request body.

Listing 13.31: Code example

1 [ HttpGet ( " { id } " ) ]


2 public IActionResult GetProduct ([ FromRoute ] int id , [ FromQuery ] string filter )
3 {
4 return Ok ( $ " Product ID : { id } , Filter : { filter } " ) ;
5 }
6

7 [ HttpPost ]
8 public IActionResult CreateProduct ([ FromBody ] Product product )

580
13.2. ASP.Net WebAPI Routing, Endpoints, Controllers, DI, Model Binding, Validations,
Versioning

9 {
10 return Ok ( product ) ;
11 }

How can you implement role-based authorization in ASP.NET Core We-


bAPI?
Role-based authorization is implemented using the ‘[Authorize(Roles = "RoleName")]‘ attribute.

Listing 13.32: Code example

1 [ Authorize ( Roles = " Admin " ) ]


2 [ ApiController ]
3 [ Route ( " api /[ controller ] " ) ]
4 public class AdminController : ControllerBase
5 {
6 [ HttpGet ]
7 public IActionResult GetAdminData ()
8 {
9 return Ok ( " Admin - specific data " ) ;
10 }
11 }

How can you implement custom model validation in ASP.NET Core We-
bAPI?
Custom validation is achieved by creating a custom attribute inheriting from ‘ValidationAttribute‘
and applying it to model properties.

Listing 13.33: Code example

1 public class CustomNameValidation : ValidationAttribute


2 {
3 protected override ValidationResult IsValid ( object value , ValidationContext
validationContext )
4 {
5 if ( value is string name && name . Contains ( " Test " ) )
6 {
7 return new ValidationResult ( " Name cannot contain ’ Test ’. " ) ;
8 }
9 return ValidationResult . Success ;
10 }
11 }
12

13 public class Product


14 {

581
13.2. ASP.Net WebAPI Routing, Endpoints, Controllers, DI, Model Binding, Validations,
Versioning

15 [ CustomNameValidation ]
16 public string Name { get ; set ; }
17 }

How do you implement content negotiation in ASP.NET Core WebAPI?


Content negotiation is automatically handled in ASP.NET Core WebAPI, but you can customize
it by adding formatters (e.g., JSON, XML).

Listing 13.34: Code example

1 public void ConfigureServices ( IServiceCollection services )


2 {
3 services . AddControllers ( options = >
4 {
5 options . RespectBrowserAcceptHeader = true ; // Enable content negotiation
6 })
7 . AddXmlSerializerFormatters () ; // Add XML formatter
8 }

What is the purpose of ‘[Controller]‘ vs ‘[ApiController]‘ in ASP.NET


Core?
- ‘[Controller]‘: Marks a class as a standard MVC controller. - ‘[ApiController]‘: Adds additional
WebAPI-specific behaviors like automatic model validation and binding source inference.

How do you implement caching in ASP.NET Core WebAPI?


Caching can be implemented using the ‘ResponseCache‘ attribute or distributed caching services.

Listing 13.35: Code example

1 [ HttpGet ]
2 [ ResponseCache ( Duration = 60 , Location = ResponseCacheLocation . Client ) ]
3 public IActionResult GetCachedData ()
4 {
5 return Ok ( new { Data = " This response is cached for 60 seconds . " }) ;
6 }

How can you handle different versions of APIs using route segments?
API versioning using route segments can be achieved by including version placeholders in the route.

582
13.2. ASP.Net WebAPI Routing, Endpoints, Controllers, DI, Model Binding, Validations,
Versioning

Listing 13.36: Code example

1 [ Route ( " api / v { version : apiVersion }/[ controller ] " ) ]


2 [ ApiVersion ( " 1.0 " ) ]
3 [ ApiVersion ( " 2.0 " ) ]
4 [ ApiController ]
5 public class ProductsController : ControllerBase
6 {
7 [ HttpGet ]
8 public IActionResult GetProductsV1 () = > Ok ( " Products from API v1 .0 " ) ;
9

10 [ HttpGet , MapToApiVersion ( " 2.0 " ) ]


11 public IActionResult GetProductsV2 () = > Ok ( " Products from API v2 .0 " ) ;
12 }

How to configure global exception handling in ASP.NET Core WebAPI?


Global exception handling can be configured using the ‘UseExceptionHandler‘ middleware.

Listing 13.37: Code example

1 public void Configure ( IApplicationBuilder app )


2 {
3 app . UseExceptionHandler ( " / error " ) ;
4

5 app . Run ( async context = >


6 {
7 if ( context . Request . Path == " / error " )
8 {
9 context . Response . StatusCode = 500;
10 await context . Response . WriteAsync ( " An unexpected error occurred . " ) ;
11 }
12 }) ;
13 }

How does the ‘[ProducesResponseType]‘ attribute enhance API docu-


mentation?
The ‘[ProducesResponseType]‘ attribute specifies the expected response type and status codes,
helping tools like Swagger generate accurate API documentation.

Listing 13.38: Code example

1 [ HttpGet ( " { id } " ) ]


2 [ ProducesResponseType ( typeof ( Product ) , StatusCodes . Status200OK ) ]
3 [ ProducesResponseType ( StatusCodes . Status404NotFound ) ]
4 public IActionResult GetProduct ( int id )

583
13.3. Caching and Performance in REST Services

5 {
6 var product = FindProductById ( id ) ;
7 if ( product == null ) return NotFound () ;
8 return Ok ( product ) ;
9 }

13.3 Caching and Performance in REST Services


How does caching improve the performance of REST services?
Caching improves the performance of REST services by storing frequently accessed data tem-
porarily, which reduces the need to recompute or retrieve it from the database on every request.
It decreases latency and the load on servers, making the API more scalable and responsive.

Listing 13.39: Code example

1 // Example : In - memory caching for a REST API


2

3 public class ProductsController : ControllerBase


4 {
5 private readonly IMemoryCache _cache ;
6 private readonly IProductService _productService ;
7

8 public ProductsController ( IMemoryCache cache , IProductService productService )


9 {
10 _cache = cache ;
11 _productService = productService ;
12 }
13

14 [ HttpGet ( " { id } " ) ]


15 public IActionResult GetProduct ( int id )
16 {
17 var cacheKey = $ " Product_ { id } " ;
18 if (! _cache . TryGetValue ( cacheKey , out Product product ) )
19 {
20 product = _productService . GetProductById ( id ) ;
21 if ( product != null )
22 {
23 _cache . Set ( cacheKey , product , TimeSpan . FromMinutes (10) ) ;
24 }
25 }
26 return Ok ( product ) ;
27 }
28 }

584
13.3. Caching and Performance in REST Services

What are the differences between client-side caching and server-side


caching in REST APIs?
Client-side caching stores the response data on the client (browser, mobile app), typically using
HTTP cache headers (e.g., ‘Cache-Control‘, ‘ETag‘). Server-side caching, on the other hand, stores
the response on the server using in-memory caches, Redis, or other caching layers.

Listing 13.40: Code example

1 // Example : Client - side caching using Cache - Control header


2

3 Cache - Control : public , max - age =3600


4 ETag : " abc123 "

How do you configure response caching in .NET Core Web API?


Response caching in .NET Core Web API is configured using the ‘[ResponseCache]‘ attribute,
which sets HTTP headers like ‘Cache-Control‘ and ‘Expires‘ to dictate caching behavior on the
client side.

Listing 13.41: Code example

1 // Example : Response caching in . NET Core


2

3 [ HttpGet ]
4 [ ResponseCache ( Duration = 60 , Location = ResponseCacheLocation . Client ) ]
5 public IActionResult GetProducts ()
6 {
7 return Ok ( productList ) ;
8 }

How do you implement Redis caching in .NET Core Web API?


Redis caching in .NET Core Web API can be implemented using the ‘StackExchangeRedis‘ package.
Redis is used as a distributed cache to store frequently accessed data, reducing the load on the
database.

Listing 13.42: Code example

1 // Example : Implementing Redis caching in . NET Core Web API


2

3 public class ProductsController : ControllerBase


4 {
5 private readonly IDistributedCache _cache ;
6 private readonly IProductService _productService ;
7

585
13.3. Caching and Performance in REST Services

8 public ProductsController ( IDistributedCache cache , IProductService


productService )
9 {
10 _cache = cache ;
11 _productService = productService ;
12 }
13

14 [ HttpGet ( " { id } " ) ]


15 public async Task < IActionResult > GetProduct ( int id )
16 {
17 var cacheKey = $ " Product_ { id } " ;
18 var cachedProduct = await _cache . GetStringAsync ( cacheKey ) ;
19

20 if ( cachedProduct == null )
21 {
22 var product = _productService . GetProductById ( id ) ;
23 if ( product != null )
24 {
25 await _cache . SetStringAsync ( cacheKey , JsonConvert . SerializeObject (
product ) ,
26 new DistributedCacheEntryOptions {
AbsoluteExpirationRelativeToNow = TimeSpan . FromMinutes (10)
}) ;
27 return Ok ( product ) ;
28 }
29 return NotFound () ;
30 }
31

32 return Ok ( JsonConvert . DeserializeObject < Product >( cachedProduct ) ) ;


33 }
34 }

What is the role of HTTP headers like ‘Cache-Control‘ and ‘ETag‘ in


REST services?
‘Cache-Control‘ defines how responses should be cached by clients and intermediate proxies. ‘ETag‘
is a unique identifier for a resource version, allowing clients to make conditional requests to mini-
mize data transfer by only downloading resources that have changed.

Listing 13.43: Code example

1 // Example : Client - side caching using Cache - Control header


2

3 Cache - Control : public , max - age =3600


4 ETag : " abc123 "

586
13.3. Caching and Performance in REST Services

How do you prevent cache poisoning in REST APIs?


Cache poisoning attacks modify or insert malicious data into caches. To prevent cache poisoning,
always validate input and use appropriate cache keys. Additionally, cache only non-sensitive or
public data and avoid caching data that depends on user input.

Listing 13.44: Code example

1 // Example : Using a unique cache key to prevent poisoning


2

3 var cacheKey = $ " Product_ { id } _User_ { userId } " ;


4 _cache . Set ( cacheKey , product ) ;

How do you invalidate a cached response in .NET Core Web API?


Cached responses in .NET Core can be invalidated by removing or updating the cache entry. This
is typically done when the underlying data changes, ensuring that the cache contains only fresh
data.

Listing 13.45: Code example

1 // Example : Invalidating cache after updating a product


2

3 [ HttpPut ( " { id } " ) ]


4 public IActionResult UpdateProduct ( int id , [ FromBody ] Product updatedProduct )
5 {
6 _productService . UpdateProduct ( id , updatedProduct ) ;
7 var cacheKey = $ " Product_ { id } " ;
8 _cache . Remove ( cacheKey ) ; // Invalidate the cache
9 return NoContent () ;
10 }

How do you measure cache hit ratio, and why is it important for perfor-
mance?
Cache hit ratio measures the percentage of cache requests that successfully retrieve data from the
cache versus requests that require fetching from the database (cache miss). A high cache hit ratio
indicates that the cache is effectively reducing database load and improving performance.

Listing 13.46: Code example

1 // Example : Logging cache hits and misses


2

3 if ( _cache . TryGetValue ( " key " , out var value ) )


4 {
5 _logger . LogInformation ( " Cache hit " ) ;

587
13.3. Caching and Performance in REST Services

6 }
7 else
8 {
9 _logger . LogInformation ( " Cache miss " ) ;
10 }

What is cache expiration, and how does it affect the performance of a


REST service?
Cache expiration defines the lifetime of cached data before it becomes stale. Properly setting cache
expiration ensures that the service returns fresh data while minimizing unnecessary recomputation
or database queries.

Listing 13.47: Code example

1 // Example : Setting cache expiration for 5 minutes


2

3 var cacheOptions = new MemoryCacheEntryOptions


4 {
5 AbsoluteExpirationRelativeToNow = TimeSpan . FromMinutes (5)
6 };
7

8 _cache . Set ( " Product_123 " , product , cacheOptions ) ;

How do you use lazy caching to improve performance in .NET Core Web
API?
Lazy caching improves performance by only caching data when a miss occurs. When a cache miss
happens, the data is retrieved from the database, stored in the cache, and returned to the user.

Listing 13.48: Code example

1 // Example : Lazy caching in a . NET Core Web API


2

3 if (! _cache . TryGetValue ( " Product_123 " , out Product product ) )


4 {
5 product = _productService . GetProductById (123) ;
6 _cache . Set ( " Product_123 " , product ) ;
7 }
8

9 return Ok ( product ) ;

588
13.3. Caching and Performance in REST Services

How to configure distributed caching in .NET Core using SQL Server?


You can configure distributed caching in .NET Core using SQL Server by setting up the ‘Mi-
crosoft.Extensions.Caching.SqlServer‘ package, which stores cache data in a SQL database.

Listing 13.49: Code example

1 // Example : Setting up SQL Server caching in Startup . cs


2

3 public void ConfigureServices ( IServiceCollection services )


4 {
5 services . AddDistributedSqlServerCache ( options = >
6 {
7 options . ConnectionString = Configuration . GetConnectionString ( "
DefaultConnection " ) ;
8 options . SchemaName = " dbo " ;
9 options . TableName = " CacheTable " ;
10 }) ;
11 }

How does database query optimization relate to caching in REST ser-


vices?
Caching reduces the need to repeatedly query the database for frequently requested data, improving
response times. Query optimization, such as indexing and avoiding expensive joins, complements
caching by reducing the time it takes to generate responses when a cache miss occurs.

Listing 13.50: Code example

1 // Example : SQL query optimization with indexing


2

3 CREATE INDEX idx_product_name ON Products ( Name ) ;

How do you implement caching in microservices to improve inter-service


communication performance?
In microservices architectures, caching can improve inter-service communication by storing fre-
quently requested data locally or in a shared distributed cache (e.g., Redis). This reduces latency
and prevents redundant service calls.

Listing 13.51: Code example

1 // Example : Using Redis for caching in a microservice


2

3 var cacheKey = $ " ServiceResponse_ { requestId } " ;


4 var cachedResponse = await _cache . GetStringAsync ( cacheKey ) ;

589
13.3. Caching and Performance in REST Services

5 if ( cachedResponse != null )
6 {
7 return JsonConvert . DeserializeObject < ServiceResponse >( cachedResponse ) ;
8 }
9

10 // If no cache , call the external service


11 var response = await _externalService . GetResponseAsync ( requestId ) ;
12 await _cache . SetStringAsync ( cacheKey , JsonConvert . SerializeObject ( response ) ) ;
13

14 return response ;

How do you implement cache partitioning in large-scale REST services?


Cache partitioning involves dividing the cache into segments based on keys or categories (e.g.,
user-specific data, product data). This helps in organizing cache data, reducing contention, and
improving scalability in large-scale systems.

Listing 13.52: Code example

1 // Example : Partitioning cache by user and product


2

3 var cacheKey = $ " User_ { userId } _Product_ { productId } " ;


4 _cache . Set ( cacheKey , product ) ;

How do you handle stale data in a cache to ensure consistency in REST


services?
Stale data in a cache can be handled by using cache invalidation strategies (e.g., time-based
expiration, event-based invalidation) to ensure that cached data is refreshed periodically or when
the underlying data changes.

Listing 13.53: Code example

1 // Example : Invalidate cache when product is updated


2

3 [ HttpPut ( " { id } " ) ]


4 public IActionResult UpdateProduct ( int id , [ FromBody ] Product product )
5 {
6 _productService . UpdateProduct ( id , product ) ;
7 _cache . Remove ( $ " Product_ { id } " ) ; // Invalidate cache
8 return NoContent () ;
9 }

590
13.3. Caching and Performance in REST Services

How do you handle cache synchronization in distributed caching across


multiple services?
In distributed caching across multiple services, cache synchronization can be handled by using
a shared cache (e.g., Redis) to ensure consistency across instances or by implementing cache
coherence protocols to invalidate or update stale data.

Listing 13.54: Code example

1 // Example : Using Redis for distributed caching in multiple services


2

3 public class ProductService


4 {
5 private readonly IDistributedCache _cache ;
6

7 public ProductService ( IDistributedCache cache )


8 {
9 _cache = cache ;
10 }
11

12 public async Task < Product > GetProductAsync ( int id )


13 {
14 var cacheKey = $ " Product_ { id } " ;
15 var cachedProduct = await _cache . GetStringAsync ( cacheKey ) ;
16 if ( cachedProduct != null )
17 {
18 return JsonConvert . DeserializeObject < Product >( cachedProduct ) ;
19 }
20

21 // Fallback to database fetch


22 var product = await _dbContext . Products . FindAsync ( id ) ;
23 await _cache . SetStringAsync ( cacheKey , JsonConvert . SerializeObject ( product )
);
24 return product ;
25 }
26 }

How do you cache REST service responses for specific clients or users?
To cache responses for specific clients or users, you can use user-specific cache keys based on
identifiers like user ID, session ID, or authentication token.

Listing 13.55: Code example

1 // Example : Caching responses for a specific user


2

3 var cacheKey = $ " User_ { userId } _ProductList " ;

591
13.3. Caching and Performance in REST Services

4 var cachedProductList = await _cache . GetStringAsync ( cacheKey ) ;


5 if ( cachedProductList == null )
6 {
7 var productList = _productService . GetUserSpecificProductList ( userId ) ;
8 await _cache . SetStringAsync ( cacheKey , JsonConvert . SerializeObject ( productList )
);
9 return Ok ( productList ) ;
10 }
11 return Ok ( JsonConvert . DeserializeObject < List < Product > >( cachedProductList ) ) ;

How do you improve performance with caching in an e-commerce REST


API?
In an e-commerce REST API, performance can be improved by caching frequently accessed data
like product listings, inventory status, and pricing. Data can be cached per user session or globally
depending on the request type.

Listing 13.56: Code example

1 // Example : Caching product catalog in an e - commerce API


2

3 [ HttpGet ( " catalog " ) ]


4 public async Task < IActionResult > GetProductCatalog ()
5 {
6 var cacheKey = " ProductCatalog " ;
7 var cachedCatalog = await _cache . GetStringAsync ( cacheKey ) ;
8 if ( cachedCatalog == null )
9 {
10 var productCatalog = _productService . GetProductCatalog () ;
11 await _cache . SetStringAsync ( cacheKey , JsonConvert . SerializeObject (
productCatalog ) ) ;
12 return Ok ( productCatalog ) ;
13 }
14 return Ok ( JsonConvert . DeserializeObject < List < Product > >( cachedCatalog ) ) ;
15 }

How do you handle large data sets in caching without consuming exces-
sive memory?
For large datasets, caching can be optimized by partitioning data, using a sliding expiration strat-
egy, and setting memory limits in caching systems like Redis. This ensures that the most frequently
used data is cached while less frequently accessed data is evicted.

Listing 13.57: Code example

592
13.4. Web API REST Principles in .NET Core

1 // Example : Sliding expiration to evict stale cache data


2

3 var cacheOptions = new MemoryCacheEntryOptions


4 {
5 SlidingExpiration = TimeSpan . FromMinutes (10)
6 };
7

8 _cache . Set ( " Product_123 " , product , cacheOptions ) ;

13.4 Web API REST Principles in .NET Core


What are the key principles of RESTful Web APIs?
RESTful Web APIs follow several principles including statelessness, client-server separation,
cacheability, uniform interface, and layered system. In .NET Core, REST APIs are typically
implemented by adhering to these principles using HTTP verbs and structured resources.

Listing 13.58: Code example

1 // Example : RESTful API implementing the GET method


2

3 [ HttpGet ]
4 public IActionResult GetAllProducts ()
5 {
6 var products = _productService . GetAll () ;
7 return Ok ( products ) ;
8 }

How do you implement statelessness in a .NET Core REST API?


Statelessness means that each request from a client must contain all the information necessary to
process it. The server does not store any state related to the client between requests. In .NET
Core, this can be achieved by not maintaining any session state on the server.

Listing 13.59: Code example

1 // Example : Stateless GET request in . NET Core


2

3 [ HttpGet ( " { id } " ) ]


4 public IActionResult GetProductById ( int id )
5 {
6 var product = _productService . GetById ( id ) ;
7 if ( product == null )
8 return NotFound () ;
9 return Ok ( product ) ;

593
13.4. Web API REST Principles in .NET Core

10 }

How do you implement the principle of a uniform interface in a .NET


Core Web API?
The uniform interface principle means that the API follows consistent patterns and methods (like
using standardized HTTP methods) across different resources. In .NET Core, you should use
the appropriate HTTP methods (GET, POST, PUT, DELETE) for CRUD operations and return
standardized HTTP status codes.

Listing 13.60: Code example

1 // Example : Uniform interface with CRUD operations


2

3 [ HttpGet ]
4 public IActionResult GetAllProducts ()
5 {
6 return Ok ( _productService . GetAll () ) ;
7 }
8

9 [ HttpPost ]
10 public IActionResult CreateProduct ([ FromBody ] Product product )
11 {
12 if (! ModelState . IsValid )
13 return BadRequest ( ModelState ) ;
14

15 _productService . Create ( product ) ;


16 return CreatedAtAction ( nameof ( GetProductById ) , new { id = product . Id } ,
product ) ;
17 }

How do you handle resource representations in a .NET Core Web API?


Resource representations refer to how data is presented to the client. In .NET Core Web APIs,
JSON and XML are commonly used representations. By default, .NET Core APIs return data in
JSON format, but you can configure it to support XML or other formats.

Listing 13.61: Code example

1 // Example : Returning JSON or XML representation in . NET Core


2

3 [ HttpGet ( " { id } " ) ]


4 public IActionResult GetProduct ( int id )
5 {
6 var product = _productService . GetById ( id ) ;

594
13.4. Web API REST Principles in .NET Core

7 if ( product == null )
8 return NotFound () ;
9

10 return Ok ( product ) ; // By default , returns JSON . Use Accept header for XML .
11 }

How do you design a REST API that conforms to the HATEOAS prin-
ciple in .NET Core?
HATEOAS (Hypermedia as the Engine of Application State) means that the API provides links
to related resources, allowing the client to navigate through the application. In .NET Core, you
can include these links in the API response.

Listing 13.62: Code example

1 // Example : Implementing HATEOAS in . NET Core API response


2

3 [ HttpGet ( " { id } " ) ]


4 public IActionResult GetProduct ( int id )
5 {
6 var product = _productService . GetById ( id ) ;
7 if ( product == null )
8 return NotFound () ;
9

10 var links = new List < Link >


11 {
12 new Link { Rel = " self " , Href = $ " / api / products /{ id } " } ,
13 new Link { Rel = " update " , Href = $ " / api / products /{ id } " , Method = " PUT " } ,
14 new Link { Rel = " delete " , Href = $ " / api / products /{ id } " , Method = " DELETE "
}
15 };
16

17 var resource = new Resource < Product >( product , links ) ;


18 return Ok ( resource ) ;
19 }

How do you manage error responses in a .NET Core Web API?


Error responses in a .NET Core Web API should follow a consistent format, typically returning
the appropriate HTTP status code along with error details in the response body. Common error
codes include 400 (Bad Request), 404 (Not Found), and 500 (Internal Server Error).

Listing 13.63: Code example

1 // Example : Standardized error response in . NET Core


2

595
13.4. Web API REST Principles in .NET Core

3 [ HttpGet ( " { id } " ) ]


4 public IActionResult GetProduct ( int id )
5 {
6 var product = _productService . GetById ( id ) ;
7 if ( product == null )
8 return NotFound ( new { error = " Product not found " }) ;
9

10 return Ok ( product ) ;
11 }

How do you ensure the security of a REST API in .NET Core using
OAuth2 or JWT?
To secure a REST API in .NET Core, you can implement OAuth2 or JWT (JSON Web Tokens)
for authentication and authorization. JWT tokens are issued after the user logs in and are used
to validate the user’s identity in subsequent requests.

Listing 13.64: Code example

1 // Example : Configuring JWT authentication in Startup . cs


2

3 public void ConfigureServices ( IServiceCollection services )


4 {
5 services . AddAuthentication ( JwtBearerDefaults . AuthenticationScheme )
6 . AddJwtBearer ( options = >
7 {
8 options . TokenValidationParameters = new TokenValidationParameters
9 {
10 ValidateIssuer = true ,
11 ValidateAudience = true ,
12 ValidateLifetime = true ,
13 ValidateIssuerSigningKey = true ,
14 ValidIssuer = " yourdomain . com " ,
15 ValidAudience = " yourdomain . com " ,
16 IssuerSigningKey = new SymmetricSecurityKey ( Encoding . UTF8 .
GetBytes ( " YourSecretKey " ) )
17 };
18 }) ;
19 }

How do you handle versioning in a .NET Core Web API?


Versioning in a .NET Core Web API allows you to make changes to your API without breaking
existing clients. You can implement versioning by using the URL path, query string, or headers.

596
13.4. Web API REST Principles in .NET Core

Listing 13.65: Code example

1 // Example : API versioning using URL path


2

3 [ ApiController ]
4 [ Route ( " api / v { version : apiVersion }/[ controller ] " ) ]
5 [ ApiVersion ( " 1.0 " ) ]
6 [ ApiVersion ( " 2.0 " ) ]
7 public class ProductsController : ControllerBase
8 {
9 [ HttpGet ]
10 [ MapToApiVersion ( " 1.0 " ) ]
11 public IActionResult GetProductsV1 ()
12 {
13 return Ok ( _productService . GetAllV1 () ) ;
14 }
15

16 [ HttpGet ]
17 [ MapToApiVersion ( " 2.0 " ) ]
18 public IActionResult GetProductsV2 ()
19 {
20 return Ok ( _productService . GetAllV2 () ) ;
21 }
22 }

How do you implement caching in a .NET Core Web API to improve


performance?
Caching in a .NET Core Web API can be implemented using in-memory caching, distributed
caching (e.g., Redis), or response caching to reduce server load and improve performance for
frequently requested data.

Listing 13.66: Code example

1 // Example : In - memory caching in . NET Core


2

3 public class ProductsController : ControllerBase


4 {
5 private readonly IMemoryCache _cache ;
6 private readonly IProductService _productService ;
7

8 public ProductsController ( IMemoryCache cache , IProductService productService )


9 {
10 _cache = cache ;
11 _productService = productService ;
12 }
13

597
13.4. Web API REST Principles in .NET Core

14 [ HttpGet ( " { id } " ) ]


15 public IActionResult GetProduct ( int id )
16 {
17 var cacheKey = $ " Product_ { id } " ;
18 if (! _cache . TryGetValue ( cacheKey , out Product product ) )
19 {
20 product = _productService . GetById ( id ) ;
21 if ( product != null )
22 {
23 _cache . Set ( cacheKey , product , TimeSpan . FromMinutes (10) ) ;
24 }
25 }
26 return Ok ( product ) ;
27 }
28 }

How do you design a REST API to be scalable in .NET Core?


A scalable REST API in .NET Core can be designed by ensuring statelessness, leveraging caching,
using asynchronous programming, and employing load balancing and distributed databases to
handle large amounts of traffic.

Listing 13.67: Code example

1 // Example : Asynchronous method in a REST API


2

3 [ HttpGet ( " { id } " ) ]


4 public async Task < IActionResult > GetProductAsync ( int id )
5 {
6 var product = await _productService . GetByIdAsync ( id ) ;
7 if ( product == null )
8 return NotFound () ;
9

10 return Ok ( product ) ;
11 }

How do you handle concurrency in a RESTful Web API using optimistic


locking in .NET Core?
Optimistic locking in a .NET Core REST API can be implemented using versioning columns (e.g.,
a timestamp or version number). When updating a resource, the API checks that the version
number has not changed, ensuring data integrity.

Listing 13.68: Code example

1 // Example : Optimistic locking using a version column

598
13.4. Web API REST Principles in .NET Core

3 [ HttpPut ( " { id } " ) ]


4 public IActionResult UpdateProduct ( int id , [ FromBody ] Product product )
5 {
6 var existingProduct = _productService . GetById ( id ) ;
7 if ( existingProduct . Version != product . Version )
8 return Conflict ( " Product version mismatch " ) ;
9

10 _productService . Update ( product ) ;


11 return NoContent () ;
12 }

How do you implement asynchronous programming in a .NET Core Web


API to improve scalability?
Asynchronous programming allows a .NET Core Web API to handle more requests by freeing
up server threads while waiting for external resources (e.g., database, network). Use ‘async‘ and
‘await‘ keywords to implement asynchronous methods.

Listing 13.69: Code example

1 // Example : Asynchronous API method in . NET Core


2

3 [ HttpGet ( " { id } " ) ]


4 public async Task < IActionResult > GetProductByIdAsync ( int id )
5 {
6 var product = await _productService . GetByIdAsync ( id ) ;
7 if ( product == null )
8 return NotFound () ;
9

10 return Ok ( product ) ;
11 }

How do you use dependency injection in a .NET Core Web API for better
testability and maintainability?
Dependency injection (DI) in .NET Core Web API allows you to inject services into controllers,
making the application more modular and easier to test. Services are registered in ‘Startup.cs‘
and injected via constructors.

Listing 13.70: Code example

1 // Example : Dependency injection in a Web API


2

3 public class ProductsController : ControllerBase

599
13.4. Web API REST Principles in .NET Core

4 {
5 private readonly IProductService _productService ;
6

7 public ProductsController ( IProductService productService )


8 {
9 _productService = productService ;
10 }
11

12 [ HttpGet ]
13 public IActionResult GetProducts ()
14 {
15 return Ok ( _productService . GetAll () ) ;
16 }
17 }

How do you implement a file upload in a .NET Core Web API?


To implement file uploads in .NET Core Web API, you can use the ‘IFormFile‘ interface to receive
the file data and save it to a specific location on the server.

Listing 13.71: Code example

1 // Example : Implementing file upload in . NET Core


2

3 [ HttpPost ( " upload " ) ]


4 public async Task < IActionResult > UploadFile ( IFormFile file )
5 {
6 if ( file == null || file . Length == 0)
7 {
8 return BadRequest ( " No file uploaded . " ) ;
9 }
10

11 var filePath = Path . Combine ( Directory . GetCurrentDirectory () , " uploads " , file .
FileName ) ;
12

13 using ( var stream = new FileStream ( filePath , FileMode . Create ) )


14 {
15 await file . CopyToAsync ( stream ) ;
16 }
17

18 return Ok ( new { filePath }) ;


19 }

600
13.4. Web API REST Principles in .NET Core

How do you use ‘ModelState.IsValid‘ in .NET Core Web API for model
validation?
‘ModelState.IsValid‘ checks whether the incoming data conforms to the model validation rules
(e.g., ‘[Required]‘, ‘[StringLength]‘). If validation fails, the API returns a ‘400 Bad Request‘ with
validation errors.

Listing 13.72: Code example

1 // Example : Using ModelState for model validation


2

3 [ HttpPost ]
4 public IActionResult CreateProduct ([ FromBody ] Product product )
5 {
6 if (! ModelState . IsValid )
7 {
8 return BadRequest ( ModelState ) ;
9 }
10

11 _productService . Create ( product ) ;


12 return Ok ( product ) ;
13 }

How do you handle pagination in a REST API with .NET Core?


Pagination in .NET Core Web APIs can be implemented by accepting page number and page size
as query parameters and returning only a subset of data based on these values.

Listing 13.73: Code example

1 // Example : Pagination in a Web API


2

3 [ HttpGet ]
4 public IActionResult GetPagedProducts ( int pageNumber = 1 , int pageSize = 10)
5 {
6 var pagedProducts = _productService . GetAll ()
7 . Skip (( pageNumber - 1) * pageSize )
8 . Take ( pageSize )
9 . ToList () ;
10 return Ok ( pagedProducts ) ;
11 }

How do you implement rate limiting in a .NET Core Web API?


Rate limiting in a .NET Core Web API can be implemented using third-party libraries like ‘Asp-
NetCoreRateLimit‘ or by using middleware to track requests and enforce limits based on client IP

601
13.4. Web API REST Principles in .NET Core

or user tokens.
Listing 13.74: Code example

1 // Example : Rate limiting using middleware in Startup . cs


2

3 public void ConfigureServices ( IServiceCollection services )


4 {
5 services . AddInMemoryRateLimiting () ;
6 services . Configure < IpRateLimitOptions >( options = >
7 {
8 options . GeneralRules = new List < RateLimitRule >
9 {
10 new RateLimitRule
11 {
12 Endpoint = " * " ,
13 Limit = 100 ,
14 Period = " 1 h "
15 }
16 };
17 }) ;
18 }

How do you manage long-running operations in a .NET Core Web API?


Long-running operations in a .NET Core Web API can be managed by using asynchronous meth-
ods, background tasks (e.g., ‘IHostedService‘), or by implementing webhooks to notify the client
once the operation completes.
Listing 13.75: Code example

1 // Example : Long - running background task using IHostedService


2

3 public class LongRunningService : IHostedService


4 {
5 private readonly IServiceProvider _serviceProvider ;
6

7 public LongRunningService ( IServiceProvider serviceProvider )


8 {
9 _serviceProvider = serviceProvider ;
10 }
11

12 public Task StartAsync ( CancellationToken cancellationToken )


13 {
14 // Code to start the long - running task
15 return Task . CompletedTask ;
16 }
17

602
13.5. Web API Security, JWTokens, JSON Tokens and Authentication

18 public Task StopAsync ( CancellationToken cancellationToken )


19 {
20 // Code to stop the long - running task
21 return Task . CompletedTask ;
22 }
23 }

13.5 Web API Security, JWTokens, JSON Tokens and Au-


thentication
How does JWT (JSON Web Token) authentication work in a .NET Core
Web API?
JWT authentication works by issuing a token after a user successfully authenticates. The token
is then sent with each subsequent request, typically in the ‘Authorization‘ header, allowing the
server to validate the user’s identity and grant access to protected resources.

Listing 13.76: Code example

1 // Example : Configuring JWT authentication in Startup . cs


2

3 public void ConfigureServices ( IServiceCollection services )


4 {
5 services . AddAuthentication ( JwtBearerDefaults . AuthenticationScheme )
6 . AddJwtBearer ( options = >
7 {
8 options . TokenValidationParameters = new TokenValidationParameters
9 {
10 ValidateIssuer = true ,
11 ValidateAudience = true ,
12 ValidateLifetime = true ,
13 ValidateIssuerSigningKey = true ,
14 ValidIssuer = " yourdomain . com " ,
15 ValidAudience = " yourdomain . com " ,
16 IssuerSigningKey = new SymmetricSecurityKey ( Encoding . UTF8 . GetBytes
( " YourSecretKey " ) )
17 };
18 }) ;
19 }

What is the structure of a JWT (JSON Web Token)?


A JWT consists of three parts separated by periods:

603
13.5. Web API Security, JWTokens, JSON Tokens and Authentication

• ‘Header‘: Contains metadata about the type of token and the hashing algorithm.
• ‘Payload‘: Contains the claims (data) about the user and other metadata.
• ‘Signature‘: Ensures the token has not been tampered with and is created using the header,
payload, and a secret key.

Listing 13.77: Code example

1 // Example : JWT token structure


2 {
3 " header " : {
4 " alg " : " HS256 " ,
5 " typ " : " JWT "
6 },
7 " payload " : {
8 " sub " : " 1234567890 " ,
9 " name " : " John Doe " ,
10 " admin " : true
11 },
12 " signature " : " encrypted_signature "
13 }

How do you generate a JWT token in a .NET Core Web API?


To generate a JWT token in .NET Core, you create claims about the user, define token parameters
such as issuer and audience, and then sign the token using a secret key.

Listing 13.78: Code example

1 // Example : Generating a JWT token in a . NET Core API


2

3 private string GenerateJwtToken ( User user )


4 {
5 var claims = new []
6 {
7 new Claim ( JwtRegisteredClaimNames . Sub , user . Id . ToString () ) ,
8 new Claim ( JwtRegisteredClaimNames . Name , user . Username ) ,
9 new Claim ( JwtRegisteredClaimNames . Jti , Guid . NewGuid () . ToString () )
10 };
11

12 var key = new SymmetricSecurityKey ( Encoding . UTF8 . GetBytes ( " YourSecretKey " ) ) ;
13 var creds = new SigningCredentials ( key , SecurityAlgorithms . HmacSha256 ) ;
14

15 var token = new JwtSecurityToken (


16 issuer : " yourdomain . com " ,
17 audience : " yourdomain . com " ,
18 claims : claims ,
19 expires : DateTime . Now . AddMinutes (30) ,

604
13.5. Web API Security, JWTokens, JSON Tokens and Authentication

20 signingCredentials : creds ) ;
21

22 return new JwtSecurityTokenHandler () . WriteToken ( token ) ;


23 }

What are the common claims used in a JWT token?


Common claims in a JWT token include:
• ‘iss‘: Issuer of the token.
• ‘sub‘: Subject (usually the user ID).
• ‘aud‘: Audience (who the token is for).
• ‘exp‘: Expiration time of the token.
• ‘iat‘: Issued at time (when the token was generated).
• ‘jti‘: JWT ID (a unique identifier for the token).

Listing 13.79: Code example

1 // Example : Claims in a JWT token


2

3 var claims = new []


4 {
5 new Claim ( JwtRegisteredClaimNames . Sub , user . Id . ToString () ) ,
6 new Claim ( JwtRegisteredClaimNames . Name , user . Username ) ,
7 new Claim ( JwtRegisteredClaimNames . Jti , Guid . NewGuid () . ToString () )
8 };

How do you secure a Web API endpoint with JWT authentication in


.NET Core?
You secure a Web API endpoint with JWT authentication by decorating the controller or ac-
tion methods with the ‘[Authorize]‘ attribute and ensuring that JWT authentication is properly
configured in ‘Startup.cs‘.

Listing 13.80: Code example

1 // Example : Securing an API endpoint with JWT


2

3 [ Authorize ]
4 [ HttpGet ( " secure - data " ) ]
5 public IActionResult GetSecureData ()
6 {
7 return Ok ( " This is a secured endpoint " ) ;
8 }

605
13.5. Web API Security, JWTokens, JSON Tokens and Authentication

How do you handle token expiration in JWT and refresh tokens in .NET
Core?
Token expiration is handled by setting the ‘exp‘ claim (expiration time) in the JWT token. To
manage token expiration, you can implement a refresh token mechanism that issues a new JWT
token when the current one expires without requiring the user to log in again.

Listing 13.81: Code example

1 // Example : Setting token expiration in a JWT token


2

3 var token = new JwtSecurityToken (


4 issuer : " yourdomain . com " ,
5 audience : " yourdomain . com " ,
6 claims : claims ,
7 expires : DateTime . Now . AddMinutes (30) , // Token expires in 30 minutes
8 signingCredentials : creds ) ;

How do you revoke a JWT token?


JWT tokens are stateless, so revoking a token requires either:
• Tracking issued tokens on the server (e.g., in a database) and checking their validity on each
request.
• Implementing a token blacklist.
Alternatively, you can reduce the token’s lifetime and rely on refresh tokens for longer sessions.

Listing 13.82: Code example

1 // Example : Revoking a JWT token ( token blacklist approach )


2

3 public async Task RevokeToken ( string token )


4 {
5 await _tokenBlacklistService . AddToBlacklistAsync ( token ) ;
6 }

How do you implement refresh tokens in a .NET Core Web API?


Refresh tokens are used to issue a new JWT token without requiring the user to log in again. You
store refresh tokens securely on the client and use them to request a new access token when the
current one expires.

Listing 13.83: Code example

1 // Example : Implementing refresh tokens


2

3 [ HttpPost ( " refresh - token " ) ]

606
13.5. Web API Security, JWTokens, JSON Tokens and Authentication

4 public IActionResult RefreshToken ([ FromBody ] RefreshTokenRequest request )


5 {
6 var refreshToken = _tokenService . ValidateRefreshToken ( request . RefreshToken ) ;
7 if ( refreshToken == null )
8 {
9 return Unauthorized () ;
10 }
11

12 var newJwtToken = _tokenService . GenerateJwtToken ( refreshToken . User ) ;


13 return Ok ( new { token = newJwtToken }) ;
14 }

How do you implement role-based authorization in a .NET Core Web


API with JWT?
Role-based authorization in .NET Core Web API with JWT is achieved by adding role claims to
the JWT token and using the ‘[Authorize(Roles = "Admin")]‘ attribute to restrict access based
on the user’s role.

Listing 13.84: Code example

1 // Example : Role - based authorization in . NET Core API


2

3 [ Authorize ( Roles = " Admin " ) ]


4 [ HttpGet ( " admin - data " ) ]
5 public IActionResult GetAdminData ()
6 {
7 return Ok ( " This is data for admins only " ) ;
8 }

How do you validate a JWT token in .NET Core?


JWT token validation in .NET Core is handled by configuring ‘JwtBearerOptions‘ to validate the
token’s issuer, audience, expiration time, and signature. The token is validated automatically for
each request based on the configuration in ‘Startup.cs‘.

Listing 13.85: Code example

1 // Example : Token validation parameters in . NET Core


2

3 public void ConfigureServices ( IServiceCollection services )


4 {
5 services . AddAuthentication ( JwtBearerDefaults . AuthenticationScheme )
6 . AddJwtBearer ( options = >
7 {
8 options . TokenValidationParameters = new TokenValidationParameters

607
13.5. Web API Security, JWTokens, JSON Tokens and Authentication

9 {
10 ValidateIssuer = true ,
11 ValidateAudience = true ,
12 ValidateLifetime = true ,
13 ValidateIssuerSigningKey = true ,
14 ValidIssuer = " yourdomain . com " ,
15 ValidAudience = " yourdomain . com " ,
16 IssuerSigningKey = new SymmetricSecurityKey ( Encoding . UTF8 . GetBytes
( " YourSecretKey " ) )
17 };
18 }) ;
19 }

How do you store JWT tokens securely on the client?


JWT tokens should be stored securely on the client side to prevent token theft. You can store
JWT tokens in:
• Local storage: Easy to use but vulnerable to XSS attacks.
• Secure cookies: More secure as they can be flagged with ‘HttpOnly‘ and ‘Secure‘ attributes,
preventing access via JavaScript and requiring HTTPS.

Listing 13.86: Code example

1 // Example : Storing JWT in localStorage ( not recommended for sensitive data )


2

3 localStorage . setItem ( " token " , jwtToken ) ;


4

5 // Example : Storing JWT in a secure cookie ( more secure )


6 document . cookie = ‘ token = $ { jwtToken }; HttpOnly ; Secure ; SameSite = Strict ‘;

How do you configure CORS (Cross-Origin Resource Sharing) to allow


JWT tokens in a .NET Core Web API?
CORS must be configured to allow cross-origin requests that include JWT tokens. You can enable
CORS in .NET Core Web API using ‘services.AddCors()‘ and defining allowed origins, headers,
and methods in ‘Startup.cs‘.

Listing 13.87: Code example

1 // Example : Configuring CORS to allow JWT tokens in . NET Core API


2

3 public void ConfigureServices ( IServiceCollection services )


4 {
5 services . AddCors ( options = >
6 {

608
13.5. Web API Security, JWTokens, JSON Tokens and Authentication

7 options . AddPolicy ( " AllowAll " ,


8 builder = >
9 {
10 builder . WithOrigins ( " https :// your - client . com " )
11 . AllowAnyHeader ()
12 . AllowAnyMethod ()
13 . AllowCredentials () ;
14 }) ;
15 }) ;
16

17 services . AddAuthentication ( JwtBearerDefaults . AuthenticationScheme ) ;


18 }
19

20 public void Configure ( IApplicationBuilder app )


21 {
22 app . UseCors ( " AllowAll " ) ;
23 app . UseAuthentication () ;
24 app . UseAuthorization () ;
25 }

How do you refresh an expired JWT token without requiring


re-authentication?
To refresh an expired JWT token without re-authentication, use a refresh token. When the JWT
token expires, the client sends the refresh token to a dedicated endpoint to receive a new JWT
without re-entering credentials.

Listing 13.88: Code example

1 // Example : Refreshing a JWT token using a refresh token


2

3 [ HttpPost ( " refresh - token " ) ]


4 public IActionResult RefreshToken ([ FromBody ] RefreshTokenRequest request )
5 {
6 var refreshToken = _tokenService . ValidateRefreshToken ( request . RefreshToken ) ;
7 if ( refreshToken == null )
8 return Unauthorized () ;
9

10 var newJwtToken = _tokenService . GenerateJwtToken ( refreshToken . User ) ;


11 return Ok ( new { token = newJwtToken }) ;
12 }

609
13.5. Web API Security, JWTokens, JSON Tokens and Authentication

What is token hijacking, and how do you prevent it in JWT authentica-


tion?
Token hijacking occurs when an attacker steals a JWT token and uses it to impersonate a user.
To prevent token hijacking:
• Use HTTPS to encrypt communication.
• Store tokens in secure cookies with the ‘HttpOnly‘ and ‘Secure‘ flags.
• Implement short token lifetimes and refresh tokens.
• Use additional security mechanisms like IP address verification or device fingerprinting.
Listing 13.89: Code example

1 // Example : Setting short JWT lifetime to mitigate token hijacking


2

3 var token = new JwtSecurityToken (


4 issuer : " yourdomain . com " ,
5 audience : " yourdomain . com " ,
6 claims : claims ,
7 expires : DateTime . Now . AddMinutes (15) , // Short lifetime for token
8 signingCredentials : creds ) ;

How do you implement multi-factor authentication (MFA) in a .NET


Core Web API with JWT?
MFA can be implemented in a .NET Core Web API by requiring a second authentication factor
(e.g., an SMS code or authentication app token) before issuing the JWT token. After validating
the user’s password, the API can issue a temporary token to complete the MFA process.
Listing 13.90: Code example

1 // Example : MFA process in a . NET Core Web API


2

3 [ HttpPost ( " authenticate " ) ]


4 public IActionResult Authenticate ([ FromBody ] LoginRequest request )
5 {
6 var user = _userService . ValidateUser ( request . Username , request . Password ) ;
7 if ( user == null )
8 return Unauthorized () ;
9

10 // Step 1: Issue temporary token to complete MFA


11 var mfaToken = _tokenService . GenerateMfaToken ( user ) ;
12 return Ok ( new { mfaToken }) ;
13 }
14

15 [ HttpPost ( " complete - mfa " ) ]


16 public IActionResult CompleteMfa ([ FromBody ] MfaRequest request )

610
13.5. Web API Security, JWTokens, JSON Tokens and Authentication

17 {
18 var isValidMfa = _mfaService . ValidateMfaToken ( request . MfaToken , request . Code ) ;
19 if (! isValidMfa )
20 return Unauthorized () ;
21

22 // Step 2: Issue full JWT token after MFA is validated


23 var jwtToken = _tokenService . GenerateJwtToken ( request . User ) ;
24 return Ok ( new { token = jwtToken }) ;
25 }

How do you detect and prevent replay attacks with JWT tokens in .NET
Core?
Replay attacks occur when an attacker captures a valid JWT token and reuses it to gain unautho-
rized access. To prevent this, you can:
• Use short token lifetimes.
• Implement token jti (JWT ID) claims with unique IDs.
• Store used tokens in a blacklist after they are used.

Listing 13.91: Code example

1 // Example : Adding jti claim to a JWT token


2

3 var claims = new []


4 {
5 new Claim ( JwtRegisteredClaimNames . Sub , user . Id . ToString () ) ,
6 new Claim ( JwtRegisteredClaimNames . Name , user . Username ) ,
7 new Claim ( JwtRegisteredClaimNames . Jti , Guid . NewGuid () . ToString () ) // Unique
token identifier
8 };

How do you protect sensitive data in the JWT payload?


Sensitive data in the JWT payload should not be stored in the token because JWT tokens can be
easily decoded. To protect sensitive data:
• Only store non-sensitive claims in the payload.
• Use encryption to secure sensitive data if it must be included.
• Use HTTPS to secure token transmission.

Listing 13.92: Code example

1 // Example : Minimal JWT payload without sensitive data


2

3 var claims = new []


4 {

611
13.6. ASP.Net Core WebApi Middleware

5 new Claim ( JwtRegisteredClaimNames . Sub , user . Id . ToString () ) , // User ID


6 new Claim ( JwtRegisteredClaimNames . Name , user . Username ) // Username
7 };

How do you log out a user and invalidate their JWT token?
Since JWT tokens are stateless and cannot be invalidated server-side, logging out a user requires
either expiring the token by waiting for its expiration time or maintaining a server-side blacklist
of tokens that are no longer valid.

Listing 13.93: Code example

1 // Example : Invalidating a JWT token by adding it to a blacklist


2

3 [ HttpPost ( " logout " ) ]


4 public async Task < IActionResult > Logout ()
5 {
6 var token = Request . Headers [ " Authorization " ]. ToString () . Replace ( " Bearer " , " " )
;
7 await _tokenBlacklistService . AddToBlacklistAsync ( token ) ;
8 return Ok ( " Logged out successfully " ) ;
9 }

13.6 ASP.Net Core WebApi Middleware


What is an Action Filter in ASP.NET Core?
An Action Filter in ASP.NET Core is a type of filter that executes custom logic before and after the
execution of an action method. It is part of the middleware pipeline and is used for cross-cutting
concerns such as logging, authentication, validation, or modifying the action’s result. Action filters
can be applied globally, at the controller level, or at the action level.

Listing 13.94: Code example

1 // Example : Custom Action Filter


2

3 public class LoggingActionFilter : IActionFilter


4 {
5 public void OnActionExecuting ( ActionExecutingContext context )
6 {
7 // Logic before the action executes
8 Console . WriteLine ( $ " Action { context . ActionDescriptor . DisplayName } is
executing . " ) ;
9 }
10

612
13.6. ASP.Net Core WebApi Middleware

11 public void OnActionExecuted ( ActionExecutedContext context )


12 {
13 // Logic after the action executes
14 Console . WriteLine ( $ " Action { context . ActionDescriptor . DisplayName } has
executed . " ) ;
15 }
16 }
17

18 // Applying the Action Filter globally in Startup . cs


19 public void ConfigureServices ( IServiceCollection services )
20 {
21 services . AddControllers ( options = >
22 {
23 options . Filters . Add < LoggingActionFilter >() ; // Add globally
24 }) ;
25 }
26

27 // Applying the Action Filter at the controller level


28 [ ServiceFilter ( typeof ( LoggingActionFilter ) ) ]
29 [ ApiController ]
30 [ Route ( " api /[ controller ] " ) ]
31 public class ProductsController : ControllerBase
32 {
33 [ HttpGet ]
34 public IActionResult GetProducts ()
35 {
36 return Ok ( new { Message = " List of products " }) ;
37 }
38 }

What is Middleware in ASP.NET Core, and how does it work in the


request pipeline?
Middleware in ASP.NET Core is a component that handles HTTP requests and responses as they
pass through the pipeline. Middleware can perform tasks such as authentication, logging, error han-
dling, or request transformation. Middleware components are configured in the ‘Startup.Configure‘
method and execute in the order they are added.

Listing 13.95: Code example

1 // Example : Simple Middleware


2 public class SimpleMiddleware
3 {
4 private readonly RequestDelegate _next ;
5

6 public SimpleMiddleware ( RequestDelegate next )

613
13.6. ASP.Net Core WebApi Middleware

7 {
8 _next = next ;
9 }
10

11 public async Task Invoke ( HttpContext context )


12 {
13 Console . WriteLine ( " Middleware before next . " ) ;
14 await _next ( context ) ; // Call the next middleware in the pipeline
15 Console . WriteLine ( " Middleware after next . " ) ;
16 }
17 }
18

19 // Adding middleware in Startup . cs


20 public void Configure ( IApplicationBuilder app )
21 {
22 app . UseMiddleware < SimpleMiddleware >() ;
23 app . Run ( async context = >
24 {
25 await context . Response . WriteAsync ( " Hello from the terminal middleware ! " ) ;
26 }) ;
27 }

How does middleware differ from filters in ASP.NET Core?


Middleware operates at a lower level in the HTTP request pipeline, processing raw requests and
responses before they reach the MVC pipeline. Filters, on the other hand, are higher-level com-
ponents that execute within the MVC pipeline and are more tightly integrated with controller
actions.

What is the difference between ‘app.Use‘, ‘app.Run‘, and ‘app.Map‘ in


ASP.NET Core?
- ‘app.Use‘: Adds middleware to the pipeline that can call the next middleware in the chain.
- ‘app.Run‘: Adds terminal middleware that ends the pipeline and does not invoke subsequent
middleware. - ‘app.Map‘: Creates a branching pipeline based on request paths.

Listing 13.96: Code example

1 public void Configure ( IApplicationBuilder app )


2 {
3 app . Use ( async ( context , next ) = >
4 {
5 Console . WriteLine ( " Middleware 1 " ) ;
6 await next () ; // Calls the next middleware
7 }) ;

614
13.6. ASP.Net Core WebApi Middleware

9 app . Map ( " / map " , mappedApp = >


10 {
11 mappedApp . Run ( async context = >
12 {
13 await context . Response . WriteAsync ( " Mapped middleware response . " ) ;
14 }) ;
15 }) ;
16

17 app . Run ( async context = >


18 {
19 await context . Response . WriteAsync ( " Final middleware response . " ) ;
20 }) ;
21 }

How do you create custom middleware in ASP.NET Core?


Custom middleware is created by defining a class with a constructor accepting a ‘RequestDelegate‘
and an ‘Invoke‘ or ‘InvokeAsync‘ method. The middleware is registered in the pipeline using
‘app.UseMiddleware<T>()‘.

Listing 13.97: Code example

1 public class CustomMiddleware


2 {
3 private readonly RequestDelegate _next ;
4

5 public CustomMiddleware ( RequestDelegate next )


6 {
7 _next = next ;
8 }
9

10 public async Task Invoke ( HttpContext context )


11 {
12 Console . WriteLine ( " Custom Middleware Logic " ) ;
13 await _next ( context ) ;
14 }
15 }
16

17 // Registering the middleware


18 public void Configure ( IApplicationBuilder app )
19 {
20 app . UseMiddleware < CustomMiddleware >() ;
21 }

615
13.6. ASP.Net Core WebApi Middleware

What is the role of ‘RequestDelegate‘ in ASP.NET Core middleware?


‘RequestDelegate‘ represents the next middleware in the pipeline. It is invoked within a middleware
component to pass control to the subsequent middleware, ensuring the request flows through the
pipeline.

How does middleware handle exceptions in the pipeline?


Middleware can catch and handle exceptions by wrapping the ‘next‘ delegate call in a ‘try-catch‘
block. The ‘app.UseExceptionHandler‘ middleware can also provide centralized error handling.

Listing 13.98: Code example

1 // Custom error - handling middleware


2 public class ErrorHandlingMiddleware
3 {
4 private readonly RequestDelegate _next ;
5

6 public ErrorHandlingMiddleware ( RequestDelegate next )


7 {
8 _next = next ;
9 }
10

11 public async Task Invoke ( HttpContext context )


12 {
13 try
14 {
15 await _next ( context ) ;
16 }
17 catch ( Exception ex )
18 {
19 Console . WriteLine ( $ " Exception caught : { ex . Message } " ) ;
20 context . Response . StatusCode = 500;
21 await context . Response . WriteAsync ( " An error occurred . " ) ;
22 }
23 }
24 }

How can middleware be conditionally applied based on the request in


ASP.NET Core?
Middleware can be conditionally applied using ‘app.UseWhen‘. This method branches the pipeline
based on a condition, applying specific middleware only when the condition is met.

Listing 13.99: Code example

616
13.6. ASP.Net Core WebApi Middleware

1 public void Configure ( IApplicationBuilder app )


2 {
3 app . UseWhen ( context = > context . Request . Path . StartsWithSegments ( " / admin " ) ,
adminApp = >
4 {
5 adminApp . Use ( async ( context , next ) = >
6 {
7 Console . WriteLine ( " Admin Middleware " ) ;
8 await next () ;
9 }) ;
10 }) ;
11

12 app . Run ( async context = >


13 {
14 await context . Response . WriteAsync ( " Default pipeline response . " ) ;
15 }) ;
16 }

What is terminal middleware, and how is it identified in ASP.NET Core?


Terminal middleware is middleware that does not call the ‘next‘ delegate and ends the request
pipeline. It is identified by the absence of a ‘next‘ call in its ‘Invoke‘ method.

How do you handle cross-origin resource sharing (CORS) in middleware?


CORS is managed using the ‘app.UseCors‘ middleware, which allows configuring cross-origin poli-
cies.

Listing 13.100: Code example

1 public void ConfigureServices ( IServiceCollection services )


2 {
3 services . AddCors ( options = >
4 {
5 options . AddPolicy ( " AllowAll " , builder = >
6 {
7 builder . AllowAnyOrigin ()
8 . AllowAnyMethod ()
9 . AllowAnyHeader () ;
10 }) ;
11 }) ;
12 }
13

14 public void Configure ( IApplicationBuilder app )


15 {
16 app . UseCors ( " AllowAll " ) ;

617
13.6. ASP.Net Core WebApi Middleware

17 app . Run ( async context = >


18 {
19 await context . Response . WriteAsync ( " CORS - enabled response . " ) ;
20 }) ;
21 }

How does middleware support dependency injection in ASP.NET Core?


Middleware constructors can accept services from the DI container, which are automatically re-
solved when the middleware is instantiated.

Listing 13.101: Code example

1 public class LoggingMiddleware


2 {
3 private readonly RequestDelegate _next ;
4 private readonly ILogger < LoggingMiddleware > _logger ;
5

6 public LoggingMiddleware ( RequestDelegate next , ILogger < LoggingMiddleware >


logger )
7 {
8 _next = next ;
9 _logger = logger ;
10 }
11

12 public async Task Invoke ( HttpContext context )


13 {
14 _logger . LogInformation ( " Request received . " ) ;
15 await _next ( context ) ;
16 }
17 }
18

19 // Registering middleware
20 public void Configure ( IApplicationBuilder app )
21 {
22 app . UseMiddleware < LoggingMiddleware >() ;
23 }

What are some common built-in middleware in ASP.NET Core, and what
do they do?
• ‘UseStaticFiles‘: Serves static files.
• ‘UseRouting‘: Matches HTTP requests to routes.
• ‘UseAuthentication‘: Processes authentication.
• ‘UseAuthorization‘: Handles user authorization.

618
13.6. ASP.Net Core WebApi Middleware

• ‘UseExceptionHandler‘: Provides centralized exception handling.

How can middleware modify the HTTP response in ASP.NET Core?


Middleware can modify the response by directly writing to the ‘HttpContext.Response‘ object.

Listing 13.102: Code example

1 public class ResponseModificationMiddleware


2 {
3 private readonly RequestDelegate _next ;
4

5 public ResponseModificationMiddleware ( RequestDelegate next )


6 {
7 _next = next ;
8 }
9

10 public async Task Invoke ( HttpContext context )


11 {
12 context . Response . Headers . Add ( " Custom - Header " , " CustomValue " ) ;
13 await _next ( context ) ;
14 }
15 }

How does ‘app.UseRouting‘ work in ASP.NET Core?


‘app.UseRouting‘ enables route matching in the middleware pipeline. It analyzes incoming requests
and maps them to endpoints defined in the application.

How does ‘app.UseEndpoints‘ work in ASP.NET Core, and how is it


different from ‘app.UseRouting‘ ?
‘app.UseEndpoints‘ is used after ‘app.UseRouting‘ to execute the matched endpoint.
While ‘app.UseRouting‘ maps requests to endpoints, ‘app.UseEndpoints‘ is responsible for execut-
ing the final endpoint logic.

Listing 13.103: Code example

1 public void Configure ( IApplicationBuilder app )


2 {
3 app . UseRouting () ;
4

5 app . UseEndpoints ( endpoints = >


6 {
7 endpoints . MapGet ( " / hello " , async context = >
8 {

619
13.6. ASP.Net Core WebApi Middleware

9 await context . Response . WriteAsync ( " Hello World ! " ) ;


10 }) ;
11 }) ;
12 }

How can you apply middleware globally in an ASP.NET Core applica-


tion?
Middleware is applied globally by adding it in the ‘Configure‘ method of the ‘Startup‘ class. All
requests pass through the middleware in the order they are added.

How can middleware be used to implement authentication in ASP.NET


Core?
Middleware like ‘UseAuthentication‘ can be added to validate user credentials, checking tokens or
cookies for authentication.

Listing 13.104: Code example

1 public void Configure ( IApplicationBuilder app )


2 {
3 app . UseAuthentication () ;
4 app . UseAuthorization () ;
5

6 app . UseEndpoints ( endpoints = >


7 {
8 endpoints . MapControllers () ;
9 }) ;
10 }

How does the middleware pipeline handle asynchronous operations?


Middleware components use asynchronous ‘Task‘-based methods to allow non-blocking execution
and ensure the pipeline continues processing other requests while waiting for I/O operations to
complete.

What happens if middleware does not call ‘await next()‘ in ASP.NET


Core?
If middleware does not call ‘await next()‘, it becomes terminal middleware, and no subsequent
middleware in the pipeline will execute. This can be intentional for cases like authentication
failures or static file handling.

620
13.6. ASP.Net Core WebApi Middleware

How do you inject configuration settings into middleware?


Configuration settings can be injected into middleware using dependency injection or by directly
accessing the configuration via ‘IConfiguration‘.

Listing 13.105: Code example

1 public class ConfigMiddleware


2 {
3 private readonly RequestDelegate _next ;
4 private readonly IConfiguration _config ;
5

6 public ConfigMiddleware ( RequestDelegate next , IConfiguration config )


7 {
8 _next = next ;
9 _config = config ;
10 }
11

12 public async Task Invoke ( HttpContext context )


13 {
14 var setting = _config [ " MySetting " ];
15 Console . WriteLine ( $ " Configuration Value : { setting } " ) ;
16 await _next ( context ) ;
17 }
18 }

Can middleware short-circuit the pipeline, and why would you do it?
Yes, middleware can short-circuit the pipeline by not calling ‘await next()‘. This is useful for sce-
narios like handling errors, rejecting unauthorized requests, or serving static files without invoking
downstream components.

How does middleware process responses in the pipeline?


Middleware processes responses in reverse order from how they are added to the pipeline. It can
modify the response after the downstream middleware completes processing.

How can middleware be used for rate limiting in ASP.NET Core?


Middleware can track request counts per client (e.g., by IP or API key) and deny requests exceeding
a predefined limit.

Listing 13.106: Code example

1 public class RateLimitingMiddleware


2 {

621
13.6. ASP.Net Core WebApi Middleware

3 private readonly RequestDelegate _next ;


4 private static Dictionary < string , int > requestCounts = new () ;
5

6 public RateLimitingMiddleware ( RequestDelegate next )


7 {
8 _next = next ;
9 }
10

11 public async Task Invoke ( HttpContext context )


12 {
13 var clientIp = context . Connection . RemoteIpAddress . ToString () ;
14 requestCounts [ clientIp ] = requestCounts . GetValueOrDefault ( clientIp , 0) +
1;
15

16 if ( requestCounts [ clientIp ] > 100)


17 {
18 context . Response . StatusCode = 429; // Too Many Requests
19 await context . Response . WriteAsync ( " Rate limit exceeded . " ) ;
20 }
21 else
22 {
23 await _next ( context ) ;
24 }
25 }
26 }

How do you write middleware to handle global request logging?


Middleware can log incoming requests, including details like HTTP method, URL, and headers.

Listing 13.107: Code example

1 public class RequestLoggingMiddleware


2 {
3 private readonly RequestDelegate _next ;
4

5 public RequestLoggingMiddleware ( RequestDelegate next )


6 {
7 _next = next ;
8 }
9

10 public async Task Invoke ( HttpContext context )


11 {
12 Console . WriteLine ( $ " Request : { context . Request . Method } { context . Request .
Path } " ) ;
13 await _next ( context ) ;
14 }

622
13.6. ASP.Net Core WebApi Middleware

15 }

How does middleware handle response compression in ASP.NET Core?


Response compression is implemented using the ‘UseResponseCompression‘ middleware, which
compresses HTTP responses for supported clients.

How can middleware handle localization in an ASP.NET Core Web API?


Localization can be managed using ‘UseRequestLocalization‘ middleware, which sets the culture
and UI culture for requests.

Listing 13.108: Code example

1 public void Configure ( IApplicationBuilder app )


2 {
3 var supportedCultures = new [] { " en - US " , " fr - FR " };
4 var localizationOptions = new RequestLocalizationOptions ()
5 . SetDefaultCulture ( supportedCultures [0])
6 . AddSupportedCultures ( supportedCultures )
7 . AddSupportedUICultures ( supportedCultures ) ;
8

9 app . UseRequestLocalization ( localizationOptions ) ;


10 app . UseRouting () ;
11 }

How does ASP.NET Core middleware support WebSocket communica-


tion?
ASP.NET Core supports WebSocket communication through the ‘UseWebSockets‘ middleware.

How do you unit test custom middleware in ASP.NET Core?


Custom middleware can be unit tested by simulating an ‘HttpContext‘ and invoking the ‘Invoke‘
method with mock dependencies.

How can middleware be used to validate API keys in ASP.NET Core


Web APIs?
Middleware can intercept requests and check for a valid API key in the headers or query string
before proceeding.

623
13.6. ASP.Net Core WebApi Middleware

Listing 13.109: Code example

1 public class ApiKeyMiddleware


2 {
3 private readonly RequestDelegate _next ;
4 private const string ApiKey = " MySecretKey " ;
5

6 public ApiKeyMiddleware ( RequestDelegate next )


7 {
8 _next = next ;
9 }
10

11 public async Task Invoke ( HttpContext context )


12 {
13 if (! context . Request . Headers . TryGetValue ( " ApiKey " , out var apiKey ) ||
apiKey != ApiKey )
14 {
15 context . Response . StatusCode = 401; // Unauthorized
16 await context . Response . WriteAsync ( " Invalid API Key " ) ;
17 return ;
18 }
19

20 await _next ( context ) ;


21 }
22 }

624
14 | Cloud Infrastructure and DevOps

Cloud infrastructure and DevOps have become integral to modern .NET development, transform-
ing how applications are built, deployed, and managed. With the rapid shift towards cloud-native
solutions, developers must be familiar with containerization, orchestration, and automated deploy-
ment strategies. This understanding enables the creation of scalable, resilient, and efficient systems
that can easily adapt to changing demands.

In today’s competitive job market, employers are increasingly looking for candidates who not
only excel at coding but also understand the full lifecycle of application delivery. Familiarity with
cloud platforms like Azure and DevOps tools for continuous integration and continuous delivery
(CI/CD) can significantly boost your employability. During interviews, demonstrating proficiency
in these areas can highlight your ability to contribute to streamlined development processes, effi-
cient operations, and rapid innovation.

By mastering cloud infrastructure and DevOps practices, you position yourself as a versatile
developer capable of bridging the gap between development and operations. This holistic skill set
not only improves the quality and reliability of your projects but also signals to potential employers
that you are well-equipped to drive success in modern, agile development environments.

14.1 Cloud Computing with Azure and Azure Services


What is Azure Service Bus, and how does it support messaging in dis-
tributed systems?
Azure Service Bus is a fully managed enterprise messaging service designed to facilitate commu-
nication between applications. It supports both message queuing and publish-subscribe patterns.
It provides reliable, asynchronous message delivery with features like dead-letter queues, message
sessions, and topic subscriptions.

Listing 14.1: Code example

1 // Example : Sending a message to Azure Service Bus queue

625
14.1. Cloud Computing with Azure and Azure Services

2 public class ServiceBusMessageSender


3 {
4 private const string connectionString = " your_connection_string " ;
5 private const string queueName = " your_queue_name " ;
6

7 public async Task SendMessageAsync ( string messageBody )


8 {
9 var client = new QueueClient ( connectionString , queueName ) ;
10 var message = new Message ( Encoding . UTF8 . GetBytes ( messageBody ) ) ;
11 await client . SendAsync ( message ) ;
12 await client . CloseAsync () ;
13 }
14 }

How do you implement autoscaling in Azure for an application hosted


on Azure App Service?
Autoscaling in Azure allows your application to automatically scale based on metrics such as CPU
usage or request count. Azure App Service provides automatic scaling by configuring scaling rules
or using predefined profiles for scale-in and scale-out operations based on demand.

Listing 14.2: Code example

1 // Example : Azure autoscale rule creation ( pseudocode , actual setup done in Azure
portal )
2

3 // Set scaling rule for CPU usage


4 AutoscaleSettings settings = new AutoscaleSettings
5 {
6 TargetCPUPercentage = 70 , // Scale up when CPU > 70%
7 MinimumInstanceCount = 2 ,
8 MaximumInstanceCount = 10
9 };
10

11 // Automatically scale based on load


12 ConfigureAutoscaling ( settings ) ;

How does Azure Functions support serverless computing, and when


should you use it?
Azure Functions is a serverless compute service that enables you to run event-driven code without
provisioning or managing servers. It automatically scales based on demand and supports a variety
of trigger types, including HTTP requests, timers, and messages from Azure Service Bus or Azure
Storage.

626
14.1. Cloud Computing with Azure and Azure Services

Listing 14.3: Code example

1 // Example : Azure Function triggered by an HTTP request


2 public static class HttpTriggerFunction
3 {
4 [ FunctionName ( " HttpTriggerFunction " ) ]
5 public static async Task < IActionResult > Run (
6 [ HttpTrigger ( AuthorizationLevel . Function , " get " , " post " , Route = null ) ]
HttpRequest req ,
7 ILogger log )
8 {
9 log . LogInformation ( " HTTP trigger function processed a request . " ) ;
10 return new OkObjectResult ( " Hello from Azure Functions ! " ) ;
11 }
12 }

What is Azure Blob Storage, and how do you interact with it in .NET?
Azure Blob Storage is a service for storing large amounts of unstructured data, such as images,
videos, or log files. It supports block blobs, append blobs, and page blobs. You can use the Azure
SDK to interact with Blob Storage by uploading, downloading, and deleting blobs.

Listing 14.4: Code example

1 // Example : Uploading a file to Azure Blob Storage


2 public class BlobStorageService
3 {
4 private const string storageConnectionString = " your_connection_string " ;
5 private const string containerName = " your_container_name " ;
6

7 public async Task UploadFileAsync ( string filePath )


8 {
9 var blobServiceClient = new BlobServiceClient ( storageConnectionString ) ;
10 var containerClient = blobServiceClient . GetBlobContainerClient (
containerName ) ;
11 var blobClient = containerClient . GetBlobClient ( Path . GetFileName ( filePath ) )
;
12

13 await blobClient . UploadAsync ( filePath , true ) ; // Upload file to Blob


Storage
14 }
15 }

627
14.1. Cloud Computing with Azure and Azure Services

How do you implement authentication and authorization in Azure App


Services using Azure Active Directory (AAD)?
Azure Active Directory (AAD) provides authentication and authorization services for Azure App
Services. You can configure Azure App Services to use AAD for user login, providing single sign-on
(SSO) and secure access. AAD tokens can be used to verify the identity and roles of users.

Listing 14.5: Code example

1 // Example : Protecting an ASP . NET Core app with Azure AD


2 public class Startup
3 {
4 public void ConfigureServices ( IServiceCollection services )
5 {
6 services . AddAuthentication ( AzureADDefaults . AuthenticationScheme )
7 . AddAzureAD ( options = > Configuration . Bind ( " AzureAd " , options ) ) ;
8 services . AddControllersWithViews () ;
9 }
10

11 public void Configure ( IApplicationBuilder app , IHostingEnvironment env )


12 {
13 app . UseAuthentication () ;
14 app . UseAuthorization () ;
15 app . UseEndpoints ( endpoints = > endpoints . MapControllers () ) ;
16 }
17 }

How do you monitor and log performance metrics of an Azure-hosted


application?
Azure Monitor provides a comprehensive solution for collecting, analyzing, and acting on telemetry
from Azure-hosted applications. It integrates with Azure Application Insights to track performance
metrics such as request rates, response times, dependency tracking, and error logs.

Listing 14.6: Code example

1 // Example : Sending custom telemetry to Azure Application Insights


2 public class TelemetryService
3 {
4 private readonly TelemetryClient _telemetryClient ;
5

6 public TelemetryService ( TelemetryClient telemetryClient )


7 {
8 _telemetryClient = telemetryClient ;
9 }
10

628
14.1. Cloud Computing with Azure and Azure Services

11 public void TrackCustomEvent ( string eventName , IDictionary < string , string >
properties )
12 {
13 _telemetryClient . TrackEvent ( eventName , properties ) ;
14 }
15 }

What is Azure Logic Apps, and how does it support workflow automa-
tion?
Azure Logic Apps is a cloud-based platform for creating workflows that integrate with various ser-
vices and systems. It allows for automation of business processes without writing code, connecting
services like Azure, Office 365, and third-party APIs through a visual designer.

Listing 14.7: Code example

1 // Example : Triggering a Logic App with an HTTP request ( set up in Azure portal )
2

3 // HTTP request to trigger the logic app


4 public async Task TriggerLogicAppAsync ( string logicAppUrl , string payload )
5 {
6 using ( var httpClient = new HttpClient () )
7 {
8 var content = new StringContent ( payload , Encoding . UTF8 , " application / json "
);
9 await httpClient . PostAsync ( logicAppUrl , content ) ;
10 }
11 }

How would you configure a virtual machine (VM) in Azure using Infras-
tructure as Code (IaC) with ARM templates?
Azure Resource Manager (ARM) templates are JSON files that define the infrastructure and
configuration for Azure resources. They allow you to deploy and configure VMs and other resources
consistently using Infrastructure as Code (IaC).

Listing 14.8: Code example

1 // Example : ARM template to deploy a VM in Azure


2 {
3 " $schema " : " https :// schema . management . azure . com / schemas /2019 -04 -01/
deploymentTemplate . json # " ,
4 " contentVersion " : " 1.0.0.0 " ,
5 " resources " : [
6 {

629
14.1. Cloud Computing with Azure and Azure Services

7 " type " : " Microsoft . Compute / virtualMachines " ,


8 " apiVersion " : " 2019 -12 -01 " ,
9 " name " : " [ variables ( ’ vmName ’) ] " ,
10 " location " : " [ resourceGroup () . location ] " ,
11 " properties " : {
12 " hardwareProfile " : {
13 " vmSize " : " Standard_DS1_v2 "
14 },
15 " osProfile " : {
16 " computerName " : " [ variables ( ’ vmName ’) ] " ,
17 " adminUsername " : " [ parameters ( ’ adminUsername ’) ] " ,
18 " adminPassword " : " [ parameters ( ’ adminPassword ’) ] "
19 },
20 ...
21 }
22 }
23 ]
24 }

What is Azure Key Vault, and how do you securely store and access
secrets in your application?
Azure Key Vault is a service that allows you to store and manage sensitive information such as
secrets, keys, and certificates. You can access Key Vault programmatically using Azure SDKs to
retrieve secrets securely in your application.

Listing 14.9: Code example

1 // Example : Retrieving a secret from Azure Key Vault in . NET


2 public class KeyVaultService
3 {
4 private readonly SecretClient _secretClient ;
5

6 public KeyVaultService ( string keyVaultUri )


7 {
8 _secretClient = new SecretClient ( new Uri ( keyVaultUri ) , new
DefaultAzureCredential () ) ;
9 }
10

11 public async Task < string > GetSecretAsync ( string secretName )


12 {
13 KeyVaultSecret secret = await _secretClient . GetSecretAsync ( secretName ) ;
14 return secret . Value ; // Return the secret value
15 }
16 }

630
14.1. Cloud Computing with Azure and Azure Services

How does Azure Traffic Manager handle load balancing across global
regions?
Azure Traffic Manager provides DNS-based load balancing to distribute traffic across multiple
Azure regions or endpoints. It supports different routing methods, such as performance, priority,
and geographic routing, to ensure high availability and low latency for users across the globe.

Listing 14.10: Code example

1 // Example : Azure Traffic Manager setup in ARM template


2 {
3 " resources " : [
4 {
5 " type " : " Microsoft . Network / trafficManagerProfiles " ,
6 " apiVersion " : " 2020 -04 -01 " ,
7 " name " : " [ parameters ( ’ trafficManagerProfileName ’) ] " ,
8 " location " : " global " ,
9 " properties " : {
10 " trafficRoutingMethod " : " Performance " ,
11 " dnsConfig " : {
12 " relativeName " : " [ parameters ( ’ dnsName ’) ] " ,
13 " ttl " : 30
14 },
15 ...
16 }
17 }
18 ]
19 }

How do you set up Azure API Management to manage, secure, and


monitor APIs?
Azure API Management is a platform that allows you to publish, secure, and monitor APIs.
It acts as a gateway for APIs, offering features like rate limiting, security policies, and version
management. You can configure API Management using the Azure portal or ARM templates.

Listing 14.11: Code example

1 // Example : ARM template to deploy Azure API Management


2 {
3 " resources " : [
4 {
5 " type " : " Microsoft . ApiManagement / service " ,
6 " apiVersion " : " 2020 -06 -01 - preview " ,
7 " name " : " [ parameters ( ’ apiManagementName ’) ] " ,
8 " location " : " [ parameters ( ’ location ’) ] " ,

631
14.1. Cloud Computing with Azure and Azure Services

9 " sku " : {


10 " name " : " Developer " ,
11 " capacity " : 1
12 },
13 " properties " : {
14 " publisherEmail " : " [ parameters ( ’ publisherEmail ’) ] " ,
15 " publisherName " : " [ parameters ( ’ publisherName ’) ] "
16 }
17 }
18 ]
19 }

How would you use Azure Storage Queues to implement reliable messag-
ing between services?
Azure Storage Queues provide a reliable messaging system that allows services to communicate
asynchronously by storing messages in a queue. Each message is processed individually, and the
queue ensures that messages are delivered in the order they were enqueued.

Listing 14.12: Code example

1 // Example : Sending a message to Azure Storage Queue


2 public class QueueMessageService
3 {
4 private const string connectionString = " your_connection_string " ;
5 private const string queueName = " your_queue_name " ;
6

7 public async Task SendMessageAsync ( string message )


8 {
9 var queueClient = new QueueClient ( connectionString , queueName ) ;
10 await queueClient . CreateIfNotExistsAsync () ;
11 await queueClient . SendMessageAsync ( message ) ; // Send message to the queue
12 }
13 }

How do you secure Azure Storage accounts to ensure data protection?


You can secure Azure Storage accounts by implementing network restrictions, enabling encryption
(Azure Storage encryption by default), and using shared access signatures (SAS) to control access
to blobs, files, queues, and tables. You can also configure Azure Active Directory (AAD) integration
for identity-based access control.

Listing 14.13: Code example

1 // Example : Creating a shared access signature ( SAS ) for a blob

632
14.1. Cloud Computing with Azure and Azure Services

2 public class BlobSasService


3 {
4 public string GenerateSasToken ( string blobUri , string accountName , string
accountKey )
5 {
6 var storageCredentials = new StorageSharedKeyCredential ( accountName ,
accountKey ) ;
7 var sasBuilder = new BlobSasBuilder
8 {
9 BlobContainerName = " your_container_name " ,
10 BlobName = " your_blob_name " ,
11 Resource = " b " , // Access to blob
12 ExpiresOn = DateTimeOffset . UtcNow . AddHours (1)
13 };
14 sasBuilder . SetPermissions ( BlobSasPermissions . Read | BlobSasPermissions .
Write ) ;
15

16 var sasToken = sasBuilder . ToSasQueryParameters ( storageCredentials ) .


ToString () ;
17 return $ " { blobUri }?{ sasToken } " ;
18 }
19 }

How do you implement disaster recovery using Azure Site Recovery?


Azure Site Recovery (ASR) provides a disaster recovery solution by replicating your applications
and VMs to another Azure region or on-premises environment. In the event of a disaster, ASR
allows you to failover to the replicated environment and continue running your services with
minimal downtime.

Listing 14.14: Code example

1 // Example : ARM template for enabling Azure Site Recovery


2 {
3 " resources " : [
4 {
5 " type " : " Microsoft . RecoveryServices / vaults " ,
6 " apiVersion " : " 2021 -06 -01 " ,
7 " name " : " [ parameters ( ’ recoveryServicesVaultName ’) ] " ,
8 " location " : " [ parameters ( ’ location ’) ] " ,
9 " properties " : {}
10 },
11 {
12 " type " : " Microsoft . RecoveryServices / vaults / replicationFabrics " ,
13 " apiVersion " : " 2021 -06 -01 " ,
14 " name " : " [ concat ( parameters ( ’ recoveryServicesVaultName ’) , ’/ Azure ’) ] " ,

633
14.1. Cloud Computing with Azure and Azure Services

15 " dependsOn " : [


16 " [ resourceId ( ’ Microsoft . RecoveryServices / vaults ’, parameters ( ’
recoveryServicesVaultName ’) ) ] "
17 ]
18 }
19 ]
20 }

What are Azure Managed Disks, and how do you use them in a VM
deployment?
Azure Managed Disks are managed by Azure, offering high availability and resilience for your VM’s
storage. They automatically handle storage scalability, backup, and redundancy. Managed Disks
are available in different tiers such as Standard HDD, Standard SSD, and Premium SSD.

Listing 14.15: Code example

1 // Example : ARM template for creating Azure Managed Disks


2 {
3 " resources " : [
4 {
5 " type " : " Microsoft . Compute / disks " ,
6 " apiVersion " : " 2021 -07 -01 " ,
7 " name " : " [ parameters ( ’ diskName ’) ] " ,
8 " location " : " [ resourceGroup () . location ] " ,
9 " sku " : {
10 " name " : " Premium_LRS "
11 },
12 " properties " : {
13 " creationData " : {
14 " createOption " : " Empty "
15 },
16 " diskSizeGB " : 128
17 }
18 }
19 ]
20 }

How do you set up Azure Cosmos DB for high-performance, globally


distributed applications?
Azure Cosmos DB is a globally distributed, multi-model database designed for high performance
and low-latency access across the globe. It offers consistency models and automatic replication

634
14.1. Cloud Computing with Azure and Azure Services

across regions. You can set up Cosmos DB with the desired partition keys, consistency levels, and
data replication policies.

Listing 14.16: Code example

1 // Example : Connecting to Azure Cosmos DB


2 public class CosmosDbService
3 {
4 private CosmosClient _client ;
5 private Container _container ;
6

7 public CosmosDbService ( string accountEndpoint , string accountKey , string


databaseId , string containerId )
8 {
9 _client = new CosmosClient ( accountEndpoint , accountKey ) ;
10 _container = _client . GetContainer ( databaseId , containerId ) ;
11 }
12

13 public async Task AddItemAsync <T >( T item )


14 {
15 await _container . CreateItemAsync ( item , new PartitionKey ( "
partition_key_value " ) ) ;
16 }
17 }

What is Azure DevOps, and how does it support CI/CD for applications
deployed on Azure?
Azure DevOps provides a suite of tools for continuous integration (CI) and continuous delivery
(CD). It supports building, testing, and deploying applications using pipelines. Azure DevOps inte-
grates with repositories (like GitHub) and cloud platforms (like Azure) for automating deployment
workflows.

Listing 14.17: Code example

1 // Example : Azure DevOps pipeline for CI / CD of an ASP . NET Core app


2 trigger :
3 - master
4

5 pool :
6 vmImage : ’ ubuntu - latest ’
7

8 steps :
9 - task : UseDotNet@2
10 inputs :
11 packageType : ’ sdk ’
12 version : ’ 5. x ’

635
14.1. Cloud Computing with Azure and Azure Services

13 installationPath : $ ( Agent . ToolsDirectory ) / dotnet


14

15 - script : |
16 dotnet restore
17 dotnet build -- configuration Release
18 dotnet test -- configuration Release
19 dotnet publish -- configuration Release -- output $ ( Build .
ArtifactStagingDirectory )
20

21 - task : PublishBuildArtifacts@1
22 inputs :
23 PathtoPublish : ’$ ( Build . ArtifactStagingDirectory ) ’
24 ArtifactName : ’ drop ’

How would you deploy a Kubernetes cluster on Azure using Azure Ku-
bernetes Service (AKS)?
Azure Kubernetes Service (AKS) is a managed Kubernetes service that allows you to deploy, man-
age, and scale containerized applications using Kubernetes. AKS simplifies cluster management
by automating key tasks like updates, patching, and scaling.

Listing 14.18: Code example

1 // Example : Deploying an AKS cluster using Azure CLI


2

3 # Create a resource group


4 az group create -- name myResourceGroup -- location eastus
5

6 # Create an AKS cluster


7 az aks create -- resource - group myResourceGroup -- name myAKSCluster -- node - count 3
-- enable - addons monitoring -- generate - ssh - keys
8

9 # Get credentials for the cluster


10 az aks get - credentials -- resource - group myResourceGroup -- name myAKSCluster

How do you secure access to Azure SQL Database using Managed Iden-
tity?
Managed Identity in Azure allows your applications to securely access Azure SQL Database without
embedding credentials in the code. It provides an identity to Azure resources, and Azure SQL can
be configured to trust this identity for database access.

Listing 14.19: Code example

1 // Example : Using Managed Identity to connect to Azure SQL Database

636
14.2. Docker and Virtual Machines (Azure, GitLab)

2 public class SqlDatabaseService


3 {
4 public async Task ConnectWithManagedIdentityAsync ()
5 {
6 var azureServiceTokenProvider = new AzureServiceTokenProvider () ;
7 var accessToken = await azureServiceTokenProvider . GetAccessTokenAsync ( "
https :// database . windows . net / " ) ;
8

9 var connectionString = new SqlConnectionStringBuilder


10 {
11 DataSource = " your_sql_server_name . database . windows . net " ,
12 InitialCatalog = " your_database_name " ,
13 AccessToken = accessToken
14 };
15

16 using ( var connection = new SqlConnection ( connectionString .


ConnectionString ) )
17 {
18 await connection . OpenAsync () ;
19 // Perform database operations
20 }
21 }
22 }

14.2 Docker and Virtual Machines (Azure, GitLab)


What is the difference between containers and virtual machines (VMs)?
Containers are lightweight, standalone, and run on a shared operating system kernel, allowing for
faster boot times and less resource consumption compared to virtual machines. Virtual machines,
on the other hand, run a full operating system including a separate kernel, which makes them
more isolated but resource-intensive.

Listing 14.20: Code example

1 // Example : No code example applicable , but a simplified analogy :


2

3 // VM : A full apartment complex with multiple apartments ( each VM runs its own OS )
.
4 // Container : Multiple rooms in the same apartment ( all containers share the host
OS ) .

637
14.2. Docker and Virtual Machines (Azure, GitLab)

How do you create and run a Docker container for a .NET Core applica-
tion?
To create and run a Docker container for a .NET Core application, you first need to create a
Dockerfile specifying the base image and build instructions. Once the Dockerfile is ready, you can
build the image and run the container using Docker commands.

Listing 14.21: Code example

1 // Dockerfile for . NET Core application


2

3 FROM mcr . microsoft . com / dotnet / aspnet :6.0 AS base


4 WORKDIR / app
5 EXPOSE 80
6

7 FROM mcr . microsoft . com / dotnet / sdk :6.0 AS build


8 WORKDIR / src
9 COPY [ " MyApp / MyApp . csproj " , " MyApp / " ]
10 RUN dotnet restore " MyApp / MyApp . csproj "
11 COPY . .
12 WORKDIR " / src / MyApp "
13 RUN dotnet build " MyApp . csproj " -c Release -o / app / build
14

15 FROM build AS publish


16 RUN dotnet publish " MyApp . csproj " -c Release -o / app / publish
17

18 FROM base AS final


19 WORKDIR / app
20 COPY -- from = publish / app / publish .
21 ENTRYPOINT [ " dotnet " , " MyApp . dll " ]

How do you persist data in a Docker container across restarts?


To persist data in Docker containers across restarts, you can use Docker volumes. Volumes provide
a way to store data outside of the container’s lifecycle, so that it remains available even if the
container is stopped or removed.

Listing 14.22: Code example

1 // Example : Using Docker volumes to persist data


2

3 docker run -d -v my_volume :/ data my_docker_image


4 # The data stored in / data will persist across container restarts or removals .

638
14.2. Docker and Virtual Machines (Azure, GitLab)

How do you set up a GitLab CI/CD pipeline to build and deploy a Docker
container?
In GitLab CI/CD, you can set up a pipeline to build and deploy a Docker container using a ‘.gitlab-
ci.yml‘ configuration file. This file defines stages (build, test, deploy) and specifies how each stage
should be executed.

Listing 14.23: Code example

1 // . gitlab - ci . yml for Docker build and deploy


2

3 stages :
4 - build
5 - deploy
6

7 build :
8 stage : build
9 image : docker :20.10
10 services :
11 - docker : dind
12 script :
13 - docker build -t myapp : latest .
14 - docker tag myapp : latest registry . gitlab . com / my - namespace / myapp : latest
15 - docker login -u " $CI_REGISTRY_USER " -p " $CI_REGISTRY_PASSWORD " " $CI_REGISTRY
"
16 - docker push registry . gitlab . com / my - namespace / myapp : latest
17

18 deploy :
19 stage : deploy
20 script :
21 - echo " Deploying to production "
22 # Add deployment steps here

How would you configure Docker Compose to manage multiple services


for a microservice architecture?
Docker Compose allows you to define multiple services in a ‘docker-compose.yml‘ file, which can be
started together. Each service can have its own Docker image, environment variables, and network
configuration.

Listing 14.24: Code example

1 // docker - compose . yml example with multiple services


2

3 version : ’ 3.8 ’
4 services :

639
14.2. Docker and Virtual Machines (Azure, GitLab)

5 web :
6 image : myapp : latest
7 ports :
8 - " 8080:80 "
9 depends_on :
10 - db
11 db :
12 image : postgres :13
13 environment :
14 POSTGRES_USER : user
15 POSTGRES_PASSWORD : password

How do you ensure the security of Docker images, both in development


and production?
Securing Docker images involves minimizing the attack surface by using minimal base images,
scanning images for vulnerabilities, regularly updating images, and using trusted sources. Addi-
tionally, use multi-stage builds to reduce the size of the final image by only including necessary
components.

Listing 14.25: Code example

1 // Example : Multi - stage Docker build for secure and minimal images
2

3 FROM mcr . microsoft . com / dotnet / sdk :6.0 AS build


4 WORKDIR / src
5 COPY [ " MyApp / MyApp . csproj " , " MyApp / " ]
6 RUN dotnet restore " MyApp / MyApp . csproj "
7 COPY . .
8 RUN dotnet publish " MyApp . csproj " -c Release -o / app
9

10 FROM mcr . microsoft . com / dotnet / aspnet :6.0 AS runtime


11 WORKDIR / app
12 COPY -- from = build / app .
13 ENTRYPOINT [ " dotnet " , " MyApp . dll " ]

How do you use Azure Container Instances (ACI) to run Docker contain-
ers in the cloud without managing the underlying VM infrastructure?
Azure Container Instances (ACI) is a service that allows you to run Docker containers in the cloud
without having to manage the underlying virtual machines. You can deploy containers on ACI
directly using the Azure CLI or ARM templates.

Listing 14.26: Code example

640
14.2. Docker and Virtual Machines (Azure, GitLab)

1 // Example : Deploying a Docker container to ACI using Azure CLI


2

3 az container create -- resource - group myResourceGroup \


4 -- name myContainer \
5 -- image myapp : latest \
6 -- cpu 1 -- memory 1.5 \
7 -- ports 80 \
8 -- dns - name - label myapp - demo

How do you deploy a Dockerized .NET application to an Azure Virtual


Machine (VM)?
To deploy a Dockerized .NET application to an Azure Virtual Machine (VM), you first create the
VM and install Docker on it. Then, you can pull the Docker image from a container registry (such
as Docker Hub or Azure Container Registry) and run it on the VM.

Listing 14.27: Code example

1 // Example : Deploying Dockerized . NET app to Azure VM


2

3 # Install Docker on the VM ( assumes Ubuntu )


4 sudo apt - get update
5 sudo apt - get install docker . io
6

7 # Pull Docker image from registry


8 docker pull myapp : latest
9

10 # Run the Docker container


11 docker run -d -p 8080:80 myapp : latest

How do you configure Docker to run as a daemon on an Azure VM?


Docker runs as a system service or daemon by default. On an Azure VM, you can ensure Docker is
enabled and running by checking the systemd service. You can configure Docker to automatically
start on system boot.

Listing 14.28: Code example

1 // Example : Configuring Docker to run as a daemon on Azure VM


2

3 # Enable Docker service to start on boot


4 sudo systemctl enable docker
5

6 # Start the Docker service


7 sudo systemctl start docker
8

641
14.2. Docker and Virtual Machines (Azure, GitLab)

9 # Check Docker service status


10 sudo systemctl status docker

How do you use Azure Container Registry (ACR) to store and manage
Docker images?
Azure Container Registry (ACR) is a managed Docker registry service that allows you to store,
manage, and deploy Docker container images. You can push Docker images to ACR and use them
in Azure services like ACI, AKS, or VMs.

Listing 14.29: Code example

1 // Example : Pushing Docker image to Azure Container Registry ( ACR )


2

3 # Log in to Azure Container Registry


4 az acr login -- name myRegistry
5

6 # Tag the Docker image for ACR


7 docker tag myapp : latest myregistry . azurecr . io / myapp : latest
8

9 # Push the Docker image to ACR


10 docker push myregistry . azurecr . io / myapp : latest

How do you scale Docker containers in Azure Kubernetes Service (AKS)?


In Azure Kubernetes Service (AKS), you can scale Docker containers by scaling the pods within a
Kubernetes deployment. You can configure AKS to automatically scale based on CPU or memory
usage using Horizontal Pod Autoscaler (HPA).

Listing 14.30: Code example

1 // Example : Scaling pods in AKS using kubectl


2

3 # Scale a deployment to 5 replicas


4 kubectl scale deployment myapp -- replicas =5
5

6 # Enable autoscaling based on CPU usage


7 kubectl autoscale deployment myapp -- cpu - percent =50 -- min =1 -- max =10

How do you manage environment variables in Docker containers for con-


figuration purposes?
Environment variables in Docker containers can be managed through the Docker CLI, Docker
Compose, or Dockerfile ‘ENV‘ instructions. These variables provide configuration settings without

642
14.2. Docker and Virtual Machines (Azure, GitLab)

hardcoding them into the application.


Listing 14.31: Code example

1 // Example : Using environment variables in Dockerfile


2

3 FROM mcr . microsoft . com / dotnet / aspnet :6.0 AS runtime


4 WORKDIR / app
5 ENV ASPNETCORE_ENVIRONMENT = Production
6 COPY ./ publish .
7 ENTRYPOINT [ " dotnet " , " MyApp . dll " ]

How do you manage secrets in Docker containers in a secure way?


Secrets in Docker containers can be securely managed using Docker secrets, which are stored in
encrypted form and accessible only to the container during runtime. For cloud environments,
secret management services like Azure Key Vault or AWS Secrets Manager can be used.
Listing 14.32: Code example

1 // Example : Using Docker secrets ( Docker Swarm mode required )


2

3 # Create a secret
4 echo " my - secret - value " | docker secret create my_secret -
5

6 # Use the secret in a service


7 docker service create -- name myapp -- secret my_secret myapp : latest

How do you monitor Docker container performance on an Azure VM?


You can monitor Docker container performance on an Azure VM by using Docker’s built-in mon-
itoring tools (such as ‘docker stats‘) or by integrating with Azure Monitor and collecting metrics
like CPU, memory, and disk usage.
Listing 14.33: Code example

1 // Example : Monitoring Docker container stats


2

3 # Monitor CPU , memory , and network usage of running containers


4 docker stats

How do you set up networking between Docker containers running on


the same Azure VM?
Docker containers running on the same host (Azure VM) can communicate with each other via
Docker networks. The default network allows containers to communicate using container names,

643
14.2. Docker and Virtual Machines (Azure, GitLab)

or you can create custom bridge networks for more control.

Listing 14.34: Code example

1 // Example : Creating a Docker bridge network and connecting containers


2

3 # Create a custom network


4 docker network create my_network
5

6 # Run containers and connect them to the network


7 docker run -d -- network my_network -- name app1 myapp : latest
8 docker run -d -- network my_network -- name app2 myapp : latest

How do you troubleshoot failed container deployments on Azure Kuber-


netes Service (AKS)?
To troubleshoot failed container deployments on AKS, you can use tools like ‘kubectl‘ to inspect
pod logs, describe pod events, and check the status of deployments. Azure Monitor and Application
Insights can also be integrated for more detailed diagnostics.

Listing 14.35: Code example

1 // Example : Using kubectl to troubleshoot pod issues


2

3 # Get the status of all pods


4 kubectl get pods
5

6 # Describe a specific pod to see events and errors


7 kubectl describe pod myapp - pod
8

9 # View logs from a failed container


10 kubectl logs myapp - pod

How do you configure GitLab CI/CD to build Docker images and push
them to Azure Container Registry (ACR)?
You can configure GitLab CI/CD to build Docker images and push them to Azure Container
Registry (ACR) by setting up a ‘.gitlab-ci.yml‘ file that defines stages for Docker build and push.
The pipeline can authenticate with ACR using Azure CLI or service principals.

Listing 14.36: Code example

1 // Example : . gitlab - ci . yml for Docker build and push to Azure Container Registry
2

3 stages :
4 - build

644
14.2. Docker and Virtual Machines (Azure, GitLab)

5 - push
6

7 build :
8 stage : build
9 image : docker :20.10
10 services :
11 - docker : dind
12 script :
13 - docker build -t myapp : latest .
14

15 push :
16 stage : push
17 script :
18 - az acr login -- name myRegistry
19 - docker tag myapp : latest myregistry . azurecr . io / myapp : latest
20 - docker push myregistry . azurecr . io / myapp : latest

How do you use Docker Compose for deploying multiple microservices


on Azure VM?
Docker Compose can be used to deploy multiple microservices on an Azure VM by defining the
services in a ‘docker-compose.yml‘ file. This file specifies how each service is built, the networks,
and the environment variables for the deployment.
Listing 14.37: Code example

1 // Example : docker - compose . yml for multiple microservices


2

3 version : ’ 3.8 ’
4 services :
5 service1 :
6 image : myapp - service1 : latest
7 ports :
8 - " 5000:5000 "
9 networks :
10 - my_network
11 service2 :
12 image : myapp - service2 : latest
13 ports :
14 - " 5001:5001 "
15 networks :
16 - my_network
17

18 networks :
19 my_network :
20 driver : bridge

645
14.3. Azure Functions

How do you scale out Docker containers in GitLab CI/CD pipeline with
parallel jobs?
In GitLab CI/CD, you can scale out Docker containers using parallel jobs, which run multiple
instances of a job in parallel. This is useful for running tests or builds on multiple containers
simultaneously.

Listing 14.38: Code example

1 // Example : GitLab CI / CD pipeline with parallel jobs


2

3 stages :
4 - test
5

6 test :
7 stage : test
8 parallel : 4
9 script :
10 - docker run myapp : latest ./ run - tests . sh

14.3 Azure Functions


What are Azure Functions and how do they enable serverless computing?
Azure Functions are a serverless compute service that allows you to run code on-demand without
provisioning or managing infrastructure. It supports event-driven execution and can be triggered
by various events such as HTTP requests, timers, or messages from other Azure services.

Listing 14.39: Code example

1 // Example : Azure Function triggered by HTTP request


2

3 public static class HttpTriggerFunction


4 {
5 [ FunctionName ( " HttpTriggerFunction " ) ]
6 public static async Task < IActionResult > Run (
7 [ HttpTrigger ( AuthorizationLevel . Function , " get " , " post " , Route = null ) ]
HttpRequest req ,
8 ILogger log )
9 {
10 log . LogInformation ( " C # HTTP trigger function processed a request . " ) ;
11 string name = req . Query [ " name " ];
12 return name != null ? ( ActionResult ) new OkObjectResult ( $ " Hello , { name } " ) :
new BadRequestObjectResult ( " Please pass a name on the query string . " ) ;
13 }
14 }

646
14.3. Azure Functions

How does Azure Functions scale automatically?


Azure Functions automatically scales based on the number of incoming events. The platform
manages the infrastructure required to scale the number of function instances depending on the
workload. It uses a consumption plan, where you only pay for the resources your functions consume.

Listing 14.40: Code example

1 // Scaling occurs automatically in the Consumption Plan .


2 // No code is required to set up scaling manually .
3 # You can view and monitor the scale in the Azure portal .

What is the difference between the Consumption Plan and Premium Plan
in Azure Functions?
In the Consumption Plan, you pay only for the time your code is running, and Azure automatically
handles scaling. The Premium Plan provides more control over scaling, includes VNET integration,
and eliminates the cold start issue by providing pre-warmed instances.

Listing 14.41: Code example

1 // Example : No specific code for setting plan , but a note on how the plans are set
2

3 # You can select the plan when creating the Azure Function App through the Azure
portal .
4 # Premium Plan offers options to pre - warm instances and scale out based on demand .

How can you use Azure Functions to trigger from Azure Blob Storage?
You can use Azure Functions with the Blob Storage trigger, which automatically runs a function
when a blob is added or updated in a specified container.

Listing 14.42: Code example

1 // Example : Azure Function triggered by Blob Storage


2

3 public static class BlobTriggerFunction


4 {
5 [ FunctionName ( " BlobTriggerFunction " ) ]
6 public static void Run (
7 [ BlobTrigger ( " samples - workitems /{ name } " , Connection = " AzureWebJobsStorage
" ) ] Stream myBlob ,
8 string name , ILogger log )
9 {
10 log . LogInformation ( $ " C # Blob trigger function Processed blob \ n Name : { name
} \ n Size : { myBlob . Length } Bytes " ) ;

647
14.3. Azure Functions

11 }
12 }

How do you configure bindings in Azure Functions?


Bindings in Azure Functions are a way to declaratively connect to other services like Blob Storage,
Cosmos DB, and Service Bus. They allow you to easily input or output data without writing the
integration code yourself.

Listing 14.43: Code example

1 // Example : Azure Function with Blob trigger and output binding to Service Bus
2

3 public static class BlobToServiceBusFunction


4 {
5 [ FunctionName ( " BlobToServiceBusFunction " ) ]
6 public static async Task Run (
7 [ BlobTrigger ( " samples - workitems /{ name } " , Connection = " AzureWebJobsStorage
" ) ] Stream myBlob ,
8 [ ServiceBus ( " myqueue " , Connection = " ServiceBusConnection " ) ]
IAsyncCollector < string > serviceBusQueue ,
9 ILogger log )
10 {
11 log . LogInformation ( $ " C # Blob trigger function Processed blob Name : { name } "
);
12 await serviceBusQueue . AddAsync ( $ " Processed blob : { name } " ) ;
13 }
14 }

How do you handle dependency injection in Azure Functions?


Azure Functions support dependency injection using the built-in Microsoft DependencyInjection
framework. You can configure your services during startup in the ‘Startup‘ class and resolve them
in your function.

Listing 14.44: Code example

1 // Example : Dependency injection in Azure Functions


2

3 public class Startup : FunctionsStartup


4 {
5 public override void Configure ( IFunctionsHostBuilder builder )
6 {
7 builder . Services . AddSingleton < IMyService , MyService >() ;
8 }
9 }

648
14.3. Azure Functions

10

11 public class MyFunction


12 {
13 private readonly IMyService _myService ;
14

15 public MyFunction ( IMyService myService )


16 {
17 _myService = myService ;
18 }
19

20 [ FunctionName ( " MyFunction " ) ]


21 public void Run ([ TimerTrigger ( " 0 */5 * * * * " ) ] TimerInfo timer , ILogger log )
22 {
23 _myService . DoWork () ;
24 log . LogInformation ( $ " C # Timer trigger function executed at : { DateTime . Now }
");
25 }
26 }

How do you implement authentication and authorization in Azure Func-


tions using Azure AD?
Azure Functions support Azure Active Directory (AAD) authentication for securing access to your
functions. By configuring the function app to require authentication, users must sign in via Azure
AD before they can access the function.

Listing 14.45: Code example

1 // Example : No specific C # code required , but this can be configured in the Azure
portal
2

3 # Go to the Azure Function App -> Authentication / Authorization .


4 # Set " App Service Authentication " to " On " and configure Azure Active Directory (
AAD ) as the provider .

How do you configure Azure Functions to process messages from Azure


Service Bus?
Azure Functions can be triggered by Azure Service Bus messages using the Service Bus binding.
You can configure it to listen for messages from a Service Bus queue or topic.

Listing 14.46: Code example

1 // Example : Azure Function triggered by Service Bus queue


2

649
14.3. Azure Functions

3 public static class ServiceBusQueueTriggerFunction


4 {
5 [ FunctionName ( " ServiceBusQueueTriggerFunction " ) ]
6 public static void Run (
7 [ ServiceBusTrigger ( " myqueue " , Connection = " ServiceBusConnection " ) ] string
message ,
8 ILogger log )
9 {
10 log . LogInformation ( $ " C # ServiceBus queue trigger function processed
message : { message } " ) ;
11 }
12 }

How do you handle retries in Azure Functions for transient failures?


Azure Functions supports retry policies for certain triggers (e.g., Service Bus, Timer). You can
define a retry policy in the function settings or code to retry execution if a transient failure occurs.

Listing 14.47: Code example

1 // Example : Setting retry policies in a Service Bus triggered function


2

3 [ FunctionName ( " ServiceBusRetryFunction " ) ]


4 [ FixedDelayRetry (5 , " 00:00:10 " ) ] // Retry 5 times with a 10 - second delay
5 public static void Run (
6 [ ServiceBusTrigger ( " myqueue " , Connection = " ServiceBusConnection " ) ] string
message ,
7 ILogger log )
8 {
9 log . LogInformation ( $ " Processed message : { message } " ) ;
10 }

What is "cold start" in Azure Functions, and how can it be mitigated?


Cold start refers to the delay that occurs when an Azure Function app scales from zero instances
to handle a new request. This delay can be mitigated by using the Premium Plan, which provides
pre-warmed instances to avoid cold starts.

Listing 14.48: Code example

1 // Example : Cold start mitigation is done by upgrading to Premium Plan .


2 // No specific code changes are required for this .

650
14.3. Azure Functions

How do you use Azure Functions with Durable Functions for orchestrat-
ing workflows?
Durable Functions is an extension of Azure Functions that allows you to write stateful functions
in a serverless environment. It provides an easy way to orchestrate workflows that involve multiple
steps or long-running processes.

Listing 14.49: Code example

1 // Example : Orchestrating with Durable Functions


2

3 public static class DurableOrchestration


4 {
5 [ FunctionName ( " OrchestrateFunction " ) ]
6 public static async Task < List < string > > RunOrchestrator (
7 [ OrchestrationTrigger ] IDurableOrchestrationContext context )
8 {
9 var outputs = new List < string >() ;
10

11 outputs . Add ( await context . CallActivityAsync < string >( " SayHello " , " Tokyo " ) ) ;
12 outputs . Add ( await context . CallActivityAsync < string >( " SayHello " , " Seattle " )
);
13 outputs . Add ( await context . CallActivityAsync < string >( " SayHello " , " London " ) )
;
14

15 return outputs ;
16 }
17

18 [ FunctionName ( " SayHello " ) ]


19 public static string SayHello ([ ActivityTrigger ] string name , ILogger log )
20 {
21 log . LogInformation ( $ " Saying hello to { name }. " ) ;
22 return $ " Hello , { name }! " ;
23 }
24 }

How do you secure secrets and configurations in Azure Functions using


Azure Key Vault?
You can securely store and retrieve secrets in Azure Functions using Azure Key Vault. You can
integrate Azure Functions with Key Vault by referencing secrets in the application settings or
retrieving them programmatically using the Azure SDK.

Listing 14.50: Code example

1 // Example : Accessing secrets from Azure Key Vault

651
14.3. Azure Functions

3 public class KeyVaultService


4 {
5 private readonly SecretClient _secretClient ;
6

7 public KeyVaultService ( string keyVaultUri )


8 {
9 _secretClient = new SecretClient ( new Uri ( keyVaultUri ) , new
DefaultAzureCredential () ) ;
10 }
11

12 public async Task < string > GetSecretAsync ( string secretName )


13 {
14 KeyVaultSecret secret = await _secretClient . GetSecretAsync ( secretName ) ;
15 return secret . Value ;
16 }
17 }

How do you implement continuous deployment for Azure Functions using


GitHub Actions?
Azure Functions can be deployed continuously using GitHub Actions. By setting up a GitHub
workflow, you can automate the deployment of function apps when changes are pushed to the
repository.

Listing 14.51: Code example

1 // Example : GitHub Actions workflow for deploying Azure Functions


2

3 name : Deploy to Azure Function


4

5 on :
6 push :
7 branches :
8 - main
9

10 jobs :
11 build - and - deploy :
12 runs - on : ubuntu - latest
13 steps :
14 - name : Checkout code
15 uses : actions / checkout@v2
16

17 - name : Set up . NET Core


18 uses : actions / setup - dotnet@v1
19 with :

652
14.3. Azure Functions

20 dotnet - version : 6. x
21

22 - name : Build project


23 run : dotnet build -- configuration Release
24

25 - name : Deploy to Azure Functions


26 uses : azure / functions - action@v1
27 with :
28 app - name : ’my - function - app ’
29 package : ’. ’
30 publish - profile : $ {{ secrets . AZURE_FUNCTIONAPP_PUBLISH_PROFILE }}

How do you test Azure Functions locally using the Azure Functions Core
Tools?
Azure Functions Core Tools allow you to test and debug Azure Functions locally. You can use
‘func start‘ to run your function locally and test it before deploying it to Azure.

Listing 14.52: Code example

1 // Example : Running Azure Functions locally


2

3 # Install Azure Functions Core Tools


4 npm install -g azure - functions - core - tools
5

6 # Run the function locally


7 func start

How do you handle application insights in Azure Functions for monitoring


and logging?
Azure Functions integrate with Azure Application Insights for monitoring and logging. You can use
Application Insights to track performance metrics, request rates, failures, and custom telemetry
data.

Listing 14.53: Code example

1 // Example : Logging custom telemetry data in Azure Functions


2

3 public static class FunctionWithTelemetry


4 {
5 private static readonly TelemetryClient _telemetryClient = new TelemetryClient
() ;
6

7 [ FunctionName ( " FunctionWithTelemetry " ) ]

653
14.3. Azure Functions

8 public static void Run ([ HttpTrigger ( AuthorizationLevel . Function , " get " , " post "
, Route = null ) ] HttpRequest req , ILogger log )
9 {
10 _telemetryClient . TrackEvent ( " FunctionExecuted " ) ;
11 log . LogInformation ( " Function executed successfully " ) ;
12 }
13 }

How do you configure CORS (Cross-Origin Resource Sharing) in Azure


Functions?
CORS can be configured in Azure Functions via the Azure portal or ‘host.json‘ file. It allows you
to specify which origins are allowed to access your function via browser requests.
Listing 14.54: Code example

1 // Example : Configuring CORS in host . json


2

3 {
4 " version " : " 2.0 " ,
5 " extensions " : {
6 " http " : {
7 " routePrefix " : " " ,
8 " cors " : {
9 " allowedOrigins " : [
10 " https :// example . com " ,
11 " https :// anotherdomain . com "
12 ]
13 }
14 }
15 }
16 }

How do you schedule recurring tasks using Azure Functions Timer Trig-
ger?
Azure Functions can be triggered on a schedule using the Timer Trigger. The schedule is defined
using a CRON expression in the ‘TimerTrigger‘ attribute.
Listing 14.55: Code example

1 // Example : Timer - triggered Azure Function


2

3 public static class TimerFunction


4 {
5 [ FunctionName ( " TimerFunction " ) ]

654
14.3. Azure Functions

6 public static void Run ([ TimerTrigger ( " 0 */5 * * * * " ) ] TimerInfo timer ,
ILogger log )
7 {
8 log . LogInformation ( $ " C # Timer trigger function executed at : { DateTime . Now }
");
9 }
10 }

How do you enable VNET (Virtual Network) integration in Azure Func-


tions?
Azure Functions on the Premium Plan support Virtual Network (VNET) integration. This allows
the function app to securely access resources in a virtual network, such as databases or internal
services.

Listing 14.56: Code example

1 // Example : VNET integration is configured in the Azure portal


2 # Go to Function App -> Networking -> VNet Integration .
3 # Enable the integration and select the appropriate VNet .

How do you configure retries and error handling in Azure Functions?


You can configure retries in Azure Functions by using attributes such as ‘[FixedDelayRetry]‘ or by
customizing error handling in the function code. Retry policies are particularly useful for transient
errors when working with external services.

Listing 14.57: Code example

1 // Example : Configuring retry logic in a Service Bus trigger


2

3 [ FunctionName ( " RetryFunction " ) ]


4 [ FixedDelayRetry (3 , " 00:00:05 " ) ] // Retry 3 times with a 5 - second delay
5 public static void Run ([ ServiceBusTrigger ( " myqueue " , Connection = "
ServiceBusConnection " ) ] string message , ILogger log )
6 {
7 log . LogInformation ( $ " Processing message : { message } " ) ;
8 }

How do you handle HTTP output bindings in Azure Functions to return


structured JSON responses?
Azure Functions HTTP output bindings allow you to easily return structured responses, such as
JSON, by using ‘OkObjectResult‘, ‘BadRequestObjectResult‘, or other IActionResult return types

655
14.4. CI/CD, Builds, and Pipelines for .NET Apps (Azure, GitLab)

in C#.

Listing 14.58: Code example

1 // Example : Returning structured JSON from an Azure Function


2

3 public static class JsonOutputFunction


4 {
5 [ FunctionName ( " JsonOutputFunction " ) ]
6 public static IActionResult Run (
7 [ HttpTrigger ( AuthorizationLevel . Function , " get " , " post " , Route = null ) ]
HttpRequest req ,
8 ILogger log )
9 {
10 var response = new { message = " Hello , world ! " , timestamp = DateTime . Now
};
11 return new OkObjectResult ( response ) ;
12 }
13 }

14.4 CI/CD, Builds, and Pipelines for .NET Apps (Azure,


GitLab)
How do you configure a basic CI/CD pipeline for a .NET Core application
in GitLab?
To configure a basic CI/CD pipeline for a .NET Core application in GitLab, you create a ‘.gitlab-
ci.yml‘ file in the root of your repository. This file defines the stages (build, test, deploy) and the
commands that GitLab will execute.

Listing 14.59: Code example

1 // . gitlab - ci . yml example for . NET Core CI / CD pipeline


2

3 stages :
4 - build
5 - test
6 - deploy
7

8 variables :
9 DOTNET_VERSION : ’ 6.0 ’
10

11 build :
12 stage : build
13 image : mcr . microsoft . com / dotnet / sdk : $DOTNET_VERSION

656
14.4. CI/CD, Builds, and Pipelines for .NET Apps (Azure, GitLab)

14 script :
15 - dotnet restore
16 - dotnet build -- configuration Release
17 artifacts :
18 paths :
19 - bin /
20

21 test :
22 stage : test
23 image : mcr . microsoft . com / dotnet / sdk : $DOTNET_VERSION
24 script :
25 - dotnet test -- configuration Release
26

27 deploy :
28 stage : deploy
29 script :
30 - echo " Deploy step here "

How can you trigger a pipeline manually in GitLab for a specific branch?
You can trigger a pipeline manually in GitLab through the GitLab UI by navigating to the project’s
CI/CD section, selecting a pipeline, and clicking the "Run pipeline" button. You can specify the
branch you want to run the pipeline for and any variables.

Listing 14.60: Code example

1 // No code required . This is done through the GitLab UI .


2 # Go to the CI / CD > Pipelines section and click on " Run pipeline ".
3 # Specify the branch and optional variables .

How do you build and publish a .NET app to Azure App Service using
Azure DevOps pipelines?
In Azure DevOps, you can create a build pipeline to compile your .NET app and a release pipeline
to publish it to Azure App Service. Azure DevOps has built-in tasks for building and deploying
.NET apps.

Listing 14.61: Code example

1 // azure - pipelines . yml example for building and deploying to Azure App Service
2

3 trigger :
4 - main
5

6 pool :
7 vmImage : ’ windows - latest ’

657
14.4. CI/CD, Builds, and Pipelines for .NET Apps (Azure, GitLab)

9 steps :
10 - task : UseDotNet@2
11 inputs :
12 packageType : ’ sdk ’
13 version : ’ 6. x ’
14

15 - task : DotNetCoreCLI@2
16 inputs :
17 command : ’ restore ’
18 projects : ’ **/*. csproj ’
19

20 - task : DotNetCoreCLI@2
21 inputs :
22 command : ’ build ’
23 projects : ’ **/*. csproj ’
24 arguments : ’ -- configuration Release ’
25

26 - task : DotNetCoreCLI@2
27 inputs :
28 command : ’ publish ’
29 publishWebProjects : true
30 arguments : ’ -- configuration Release -- output $ ( Build . ArtifactStagingDirectory )

31 zipAfterPublish : true
32

33 - task : AzureWebApp@1
34 inputs :
35 azureSubscription : ’ < your - azure - subscription > ’
36 appType : ’ webApp ’
37 appName : ’ < your - app - name > ’
38 package : ’$ ( Build . ArtifactStagingDirectory ) /*. zip ’

What are build artifacts in a CI/CD pipeline, and how are they managed
in Azure DevOps?
Build artifacts are the output of the build process, such as compiled binaries, configuration files, or
other resources. In Azure DevOps, artifacts can be stored, published, and shared between stages
of the pipeline for deployment.

Listing 14.62: Code example

1 // Example : Publishing build artifacts in Azure DevOps pipeline


2

3 steps :
4 - task : DotNetCoreCLI@2

658
14.4. CI/CD, Builds, and Pipelines for .NET Apps (Azure, GitLab)

5 inputs :
6 command : ’ publish ’
7 arguments : ’ -- configuration Release -- output $ ( Build . ArtifactStagingDirectory )

8

9 - task : PublishBuildArtifacts@1
10 inputs :
11 PathtoPublish : ’$ ( Build . ArtifactStagingDirectory ) ’
12 ArtifactName : ’ drop ’

How do you use GitLab CI/CD pipeline environments for deploying to


different stages (e.g., dev, staging, production)?
GitLab environments represent the stages where the code is deployed (e.g., dev, staging, produc-
tion). You can define environments in your ‘.gitlab-ci.yml‘ file and specify different steps for each
environment.
Listing 14.63: Code example

1 // Example : GitLab pipeline with environments


2

3 stages :
4 - build
5 - deploy
6

7 build :
8 stage : build
9 script :
10 - dotnet build -- configuration Release
11 artifacts :
12 paths :
13 - bin /
14

15 deploy_dev :
16 stage : deploy
17 environment :
18 name : dev
19 script :
20 - echo " Deploying to development environment "
21

22 deploy_prod :
23 stage : deploy
24 environment :
25 name : production
26 script :
27 - echo " Deploying to production environment "

659
14.4. CI/CD, Builds, and Pipelines for .NET Apps (Azure, GitLab)

How do you add a build badge to a .NET project in GitLab?


You can add a build badge to a .NET project in GitLab by using the badge URL provided in the
project’s CI/CD settings. The badge will show the current status of the pipeline (passed, failed,
etc.).

Listing 14.64: Code example

1 // Example : Markdown to add a build badge in the README . md


2

3 ![ Pipeline ]( https :// gitlab . com / < namespace >/ < project >/ badges / main / pipeline . svg )

What is a "self-hosted runner" in GitLab CI, and when would you use
one?
A self-hosted runner is a GitLab CI runner that you install on your own infrastructure. You
use a self-hosted runner when you need custom build environments, more control over the build
environment, or when you want to avoid limitations of shared GitLab runners.

Listing 14.65: Code example

1 // Example : Registering a self - hosted GitLab runner


2

3 sudo gitlab - runner register


4 # Follow the prompts to configure the runner .
5 # You will need the GitLab URL and runner registration token .

How do you implement a multi-stage pipeline in Azure DevOps for build-


ing, testing, and deploying a .NET app?
In Azure DevOps, a multi-stage pipeline can be defined in YAML. Each stage represents a different
step in the CI/CD process, such as building, testing, and deploying. Stages can be executed
sequentially or in parallel.

Listing 14.66: Code example

1 // Example : Multi - stage pipeline in Azure DevOps


2

3 stages :
4 - stage : Build
5 jobs :
6 - job : Build
7 pool :
8 vmImage : ’ windows - latest ’
9 steps :
10 - task : DotNetCoreCLI@2

660
14.4. CI/CD, Builds, and Pipelines for .NET Apps (Azure, GitLab)

11 inputs :
12 command : ’ build ’
13 arguments : ’ -- configuration Release ’
14

15 - stage : Test
16 jobs :
17 - job : Test
18 pool :
19 vmImage : ’ windows - latest ’
20 steps :
21 - task : DotNetCoreCLI@2
22 inputs :
23 command : ’ test ’
24 arguments : ’ -- configuration Release ’
25

26 - stage : Deploy
27 jobs :
28 - job : Deploy
29 pool :
30 vmImage : ’ windows - latest ’
31 steps :
32 - task : AzureWebApp@1
33 inputs :
34 azureSubscription : ’ < your - azure - subscription > ’
35 appName : ’ < your - app - name > ’
36 package : ’$ ( Build . ArtifactStagingDirectory ) /*. zip ’

How do you run tests in a GitLab CI/CD pipeline for a .NET project?
In GitLab, you can run tests for a .NET project by adding a test stage to your ‘.gitlab-ci.yml‘ file.
You use the ‘dotnet test‘ command to execute the tests in your solution.

Listing 14.67: Code example

1 // Example : Running tests in a GitLab pipeline


2

3 stages :
4 - build
5 - test
6

7 build :
8 stage : build
9 script :
10 - dotnet build -- configuration Release
11

12 test :
13 stage : test

661
14.4. CI/CD, Builds, and Pipelines for .NET Apps (Azure, GitLab)

14 script :
15 - dotnet test -- configuration Release

How do you automate database migrations in a CI/CD pipeline using


.NET EF Core?
You can automate database migrations in a CI/CD pipeline using Entity Framework Core (EF
Core). In the pipeline, you run the ‘dotnet ef database update‘ command to apply migrations as
part of the deployment process.

Listing 14.68: Code example

1 // Example : Automating database migrations in Azure DevOps pipeline


2

3 stages :
4 - build
5 - deploy
6

7 deploy :
8 stage : deploy
9 script :
10 - dotnet ef database update

How do you handle secrets in a CI/CD pipeline (e.g., database passwords)


securely?
Secrets such as database passwords should be stored securely using secret management features
provided by CI/CD platforms. In GitLab, you use CI/CD variables, and in Azure DevOps, you
use pipeline variables or Azure Key Vault integration to handle secrets securely.

Listing 14.69: Code example

1 // Example : Using GitLab CI / CD variables for secret management


2

3 stages :
4 - deploy
5

6 deploy :
7 stage : deploy
8 script :
9 - echo " Deploying with secret DB_PASSWORD : $DB_PASSWORD "

662
14.4. CI/CD, Builds, and Pipelines for .NET Apps (Azure, GitLab)

How do you configure YAML pipelines in Azure DevOps to deploy a


containerized .NET application?
In Azure DevOps, you can configure YAML pipelines to build and deploy a containerized .NET
application by adding steps for Docker build, push, and Kubernetes deployment.

Listing 14.70: Code example

1 // Example : Azure DevOps YAML pipeline for containerized . NET application


2

3 trigger :
4 - main
5

6 pool :
7 vmImage : ’ ubuntu - latest ’
8

9 steps :
10 - task : Docker@2
11 inputs :
12 containerRegistry : ’ myContainerRegistry ’
13 repository : ’ myapp ’
14 command : ’ buildAndPush ’
15 dockerfile : ’ **/ Dockerfile ’
16 tags : ’$ ( Build . BuildId ) ’
17

18 - task : KubernetesManifest@0
19 inputs :
20 action : ’ deploy ’
21 kubernetesServiceConnection : ’ myKubernetesServiceConnection ’
22 namespace : ’ default ’
23 manifests : ’ **/*. yaml ’

How do you configure pipeline caching in GitLab CI/CD to speed up


builds?
Pipeline caching in GitLab CI/CD allows you to cache dependencies or build artifacts between jobs
or pipeline runs, speeding up the build process. You can define cache paths in the ‘.gitlab-ci.yml‘
file.

Listing 14.71: Code example

1 // Example : GitLab pipeline caching


2

3 cache :
4 paths :
5 - . nuget / packages /

663
14.4. CI/CD, Builds, and Pipelines for .NET Apps (Azure, GitLab)

7 build :
8 stage : build
9 script :
10 - dotnet restore
11 - dotnet build -- configuration Release

How do you manage multiple environments in Azure DevOps pipelines


(e.g., development, staging, production)?
In Azure DevOps, you can manage multiple environments by defining separate stages in the YAML
pipeline for each environment. You can also use environment-specific variables and approvals.

Listing 14.72: Code example

1 // Example : Azure DevOps pipeline with multiple environments


2

3 stages :
4 - stage : Deploy_Dev
5 jobs :
6 - job : Deploy
7 pool :
8 vmImage : ’ windows - latest ’
9 steps :
10 - task : AzureWebApp@1
11 inputs :
12 appName : ’ myapp - dev ’
13

14 - stage : Deploy_Prod
15 jobs :
16 - job : Deploy
17 pool :
18 vmImage : ’ windows - latest ’
19 steps :
20 - task : AzureWebApp@1
21 inputs :
22 appName : ’ myapp - prod ’

How do you trigger a GitLab CI/CD pipeline only when changes are
made to specific files?
You can trigger a GitLab CI/CD pipeline only when changes are made to specific files using the
‘only‘ and ‘changes‘ keywords in the ‘.gitlab-ci.yml‘ file.

Listing 14.73: Code example

664
14.4. CI/CD, Builds, and Pipelines for .NET Apps (Azure, GitLab)

1 // Example : GitLab pipeline trigger based on file changes


2

3 stages :
4 - build
5

6 build :
7 stage : build
8 script :
9 - dotnet build
10 only :
11 changes :
12 - src /**

How do you implement parallel jobs in GitLab CI/CD for faster build
and test processes?
You can implement parallel jobs in GitLab CI/CD by using the ‘parallel‘ keyword, which allows
multiple instances of a job to run concurrently, speeding up build and test processes.
Listing 14.74: Code example

1 // Example : Running parallel jobs in GitLab


2

3 stages :
4 - test
5

6 test :
7 stage : test
8 parallel : 4
9 script :
10 - dotnet test -- configuration Release

How do you run a SonarQube analysis for code quality checks in a GitLab
pipeline?
To run a SonarQube analysis in a GitLab pipeline, you configure a stage in your ‘.gitlab-ci.yml‘
file to use the SonarQube scanner for .NET projects.
Listing 14.75: Code example

1 // Example : GitLab pipeline with SonarQube analysis


2

3 stages :
4 - build
5 - test
6 - scan

665
14.4. CI/CD, Builds, and Pipelines for .NET Apps (Azure, GitLab)

8 sonarqube :
9 stage : scan
10 image : sonarsource / sonar - scanner - cli : latest
11 script :
12 - sonar - scanner
13 - Dsonar . projectKey = my - dotnet - project
14 - Dsonar . host . url = https :// sonar . example . com
15 - Dsonar . login = $SONAR_TOKEN

How do you configure approvals and gates for production deployment in


Azure DevOps?
In Azure DevOps, you can configure approvals and gates for production deployment by setting up
environment approvals and adding checks, such as quality gates, to ensure certain conditions are
met before deployment.

Listing 14.76: Code example

1 // Example : No specific C # code required , but this can be configured in the Azure
DevOps portal
2

3 # Go to Pipelines > Environments > Add checks to configure approvals and gates .
4 # You can add manual approval steps or integrate with quality gates like SonarQube
.

How do you configure Horizontal Scaling for GitLab Runners in a CI/CD


pipeline?
Horizontal scaling of GitLab Runners involves adding more runners to handle the load of concurrent
jobs. You can register multiple GitLab Runners on different machines or use Docker-based runners
with Kubernetes to scale dynamically.

Listing 14.77: Code example

1 // Example : Registering a new GitLab Runner


2

3 sudo gitlab - runner register -- url https : // gitlab . example . com / -- registration - token
< your - token >

How do you optimize the performance of .NET applications running in


Azure App Services?
To optimize the performance of .NET applications in Azure App Services, you can:

666
14.4. CI/CD, Builds, and Pipelines for .NET Apps (Azure, GitLab)

• Use autoscaling to handle fluctuating traffic.


• Enable Azure Redis Cache for session and data caching.
• Implement asynchronous operations to free up server resources.
• Use the Premium or Isolated App Service Plan for enhanced performance and isolation.
• Profile and monitor with Application Insights to identify bottlenecks.

Listing 14.78: Code example

1 // Example : Asynchronous method in . NET to improve scalability


2

3 public async Task < string > GetDataAsync ()


4 {
5 var result = await httpClient . GetStringAsync ( " https :// api . example . com / data " ) ;
6 return result ;
7 }

How do you implement database sharding for scalability in Azure SQL?


Database sharding is a technique to improve database scalability by splitting large databases into
smaller, more manageable pieces (shards). Each shard contains a subset of the data. In Azure
SQL, you can implement sharding by manually partitioning data across multiple databases.

Listing 14.79: Code example

1 // Example : Basic sharding logic based on a customer ID


2

3 public string GetShardConnectionString ( int customerId )


4 {
5 if ( customerId % 2 == 0)
6 return " Server = shard1 ; Database = myDatabase1 ; " ;
7 else
8 return " Server = shard2 ; Database = myDatabase2 ; " ;
9 }

How do you manage and optimize long-running processes in Azure Func-


tions for better scalability?
For long-running processes, Azure Functions can be paired with Durable Functions, which allow
you to orchestrate and manage stateful workflows. This prevents timeouts and enables better
scalability by breaking down long-running tasks into manageable steps.

Listing 14.80: Code example

1 // Example : Using Durable Functions to manage long - running processes


2

3 [ FunctionName ( " OrchestrateLongRunningTask " ) ]

667
14.4. CI/CD, Builds, and Pipelines for .NET Apps (Azure, GitLab)

4 public async Task < List < string > > RunOrchestrator (
5 [ OrchestrationTrigger ] IDurableOrchestrationContext context )
6 {
7 var results = new List < string >() ;
8 results . Add ( await context . CallActivityAsync < string >( " Task1 " , null ) ) ;
9 results . Add ( await context . CallActivityAsync < string >( " Task2 " , null ) ) ;
10 return results ;
11 }

How do you monitor and tune the performance of GitLab Runners in a


self-hosted environment?
You can monitor and tune the performance of GitLab Runners by tracking CPU, memory, and
disk usage on the runner machines. Tuning involves adjusting the number of concurrent jobs, using
a cache to reduce build times, and distributing jobs across multiple runners.

How do you set up load balancing in Azure to improve application per-


formance?
Azure Load Balancer distributes incoming traffic across multiple virtual machines to improve
performance and availability. You can configure load balancing rules and health probes to ensure
traffic is routed to healthy instances.

Listing 14.81: Code example

1 // Example : ARM template to deploy Azure API Management


2 {
3 " resources " : [
4 {
5 " type " : " Microsoft . ApiManagement / service " ,
6 " apiVersion " : " 2020 -06 -01 - preview " ,
7 " name " : " [ parameters ( ’ apiManagementName ’) ] " ,
8 " location " : " [ parameters ( ’ location ’) ] " ,
9 " sku " : {
10 " name " : " Developer " ,
11 " capacity " : 1
12 },
13 " properties " : {
14 " publisherEmail " : " [ parameters ( ’ publisherEmail ’) ] " ,
15 " publisherName " : " [ parameters ( ’ publisherName ’) ] "
16 }
17 }
18 ]
19 }

668
14.4. CI/CD, Builds, and Pipelines for .NET Apps (Azure, GitLab)

How do you scale out an application in Azure App Service using deploy-
ment slots?
Deployment slots in Azure App Service allow you to deploy multiple versions of your application
(e.g., dev, staging, production). You can swap slots to scale out traffic or perform zero-downtime
deployments.

How do you implement Distributed Tracing in Azure for monitoring mi-


croservice performance?
Distributed tracing helps track requests across multiple services in a microservices architecture.
Azure Application Insights supports distributed tracing by automatically correlating telemetry
from different services using operation IDs.

Listing 14.82: Code example

1 // Example : Sending telemetry with correlation IDs for distributed tracing


2

3 TelemetryClient telemetryClient = new TelemetryClient () ;


4 var operation = telemetryClient . StartOperation < RequestTelemetry >( " MyOperation " ) ;
5

6 try
7 {
8 // Perform some work
9 }
10 finally
11 {
12 telemetryClient . StopOperation ( operation ) ;
13 }

How do you optimize SQL queries for better performance in a cloud-based


.NET application?
To optimize SQL queries for better performance, you can:
• Use indexes to speed up queries.
• Avoid unnecessary SELECT * queries; instead, select only the required columns.
• Optimize JOIN operations.
• Use query execution plans to identify bottlenecks.
• Implement pagination for large result sets.

Listing 14.83: Code example

1 // Example : Optimizing a SQL query with indexes


2

669
14.4. CI/CD, Builds, and Pipelines for .NET Apps (Azure, GitLab)

3 CREATE INDEX idx_customer_name ON Customers ( FirstName , LastName ) ;

670
15 | Security in .NET Applications

Security is a critical pillar in the development of .NET applications, addressing aspects such as au-
thentication, authorization, encryption, and secure data handling. By integrating robust security
measures into your software, you can protect sensitive information and mitigate risks associated
with common vulnerabilities like injection attacks and cross-site scripting. This knowledge is in-
dispensable for ensuring that your applications are not only functional but also resilient against
evolving security threats.

In today’s job market, a solid understanding of security in .NET is a significant asset. Employers
are keen to see candidates who can articulate secure coding practices and demonstrate familiarity
with tools and frameworks—such as ASP.NET Identity—that bolster application security. In
interviews, showcasing your ability to design and implement secure systems not only evidences
technical proficiency but also highlights your commitment to building trustworthy, reliable software
solutions.

15.1 General Code Security and Best Practices in .NET


What is input validation, and why is it important in .NET applications?
Input validation ensures that the data received by an application is safe, valid, and conforms to
expected formats. It is critical in preventing common security vulnerabilities such as SQL injection,
cross-site scripting (XSS), and command injection by ensuring that inputs are properly sanitized
before use.

Listing 15.1: Code example

1 // Example : Input validation using Regex to ensure a valid email format


2 public bool IsValidEmail ( string email )
3 {
4 var emailRegex = new Regex ( @ " ^[^ @ \ s ]+ @ [^ @ \ s ]+\.[^ @ \ s ]+ $ " ) ;
5 return emailRegex . IsMatch ( email ) ;
6 }

671
15.1. General Code Security and Best Practices in .NET

How do you prevent SQL injection in .NET applications?


SQL injection can be prevented by using parameterized queries or stored procedures instead of
concatenating user inputs into SQL statements. Parameterized queries ensure that inputs are
treated as data rather than executable code.

Listing 15.2: Code example

1 // Example : Preventing SQL injection using parameterized queries


2 public void GetUserById ( int userId )
3 {
4 using ( var connection = new SqlConnection ( connectionString ) )
5 {
6 var command = new SqlCommand ( " SELECT * FROM Users WHERE Id = @userId " ,
connection ) ;
7 command . Parameters . AddWithValue ( " @userId " , userId ) ;
8

9 connection . Open () ;
10 var reader = command . ExecuteReader () ;
11 // Process results
12 }
13 }

What is cross-site scripting (XSS), and how do you prevent it in .NET


applications?
Cross-site scripting (XSS) is a vulnerability that allows attackers to inject malicious scripts into
web pages viewed by other users. To prevent XSS, you should always encode user inputs that
are rendered on web pages and avoid directly embedding untrusted data in HTML, JavaScript, or
URLs.

Listing 15.3: Code example

1 // Example : Preventing XSS by encoding output in Razor views ( ASP . NET )


2 @Html . Encode ( Model . UserInput )

What are secure coding practices for handling passwords in .NET?


When handling passwords, use hashing algorithms like PBKDF2, bcrypt, or Argon2 with a salt to
securely store passwords. Never store passwords in plain text, and always ensure that the hashing
algorithm is resistant to brute force attacks.

Listing 15.4: Code example

1 // Example : Hashing a password using PBKDF2 in . NET


2 public string HashPassword ( string password )

672
15.1. General Code Security and Best Practices in .NET

3 {
4 using ( var rfc2898DeriveBytes = new Rfc2898DeriveBytes ( password , 16 , 10000) )
5 {
6 byte [] salt = rfc2898DeriveBytes . Salt ;
7 byte [] hash = rfc2898DeriveBytes . GetBytes (32) ;
8 return Convert . ToBase64String ( salt ) + " : " + Convert . ToBase64String ( hash ) ;
9 }
10 }

How can you protect sensitive data using encryption in .NET applica-
tions?
Sensitive data should be encrypted both at rest and in transit. In .NET, use libraries such as
‘System.Security.Cryptography‘ to encrypt data using strong encryption algorithms like AES (Ad-
vanced Encryption Standard).

Listing 15.5: Code example

1 // Example : Encrypting sensitive data using AES


2 public string Encrypt ( string plainText , string key )
3 {
4 using ( var aes = Aes . Create () )
5 {
6 aes . Key = Encoding . UTF8 . GetBytes ( key ) ;
7 aes . GenerateIV () ;
8 var iv = aes . IV ;
9 var encryptor = aes . CreateEncryptor ( aes . Key , iv ) ;
10

11 using ( var ms = new MemoryStream () )


12 {
13 ms . Write ( iv , 0 , iv . Length ) ;
14 using ( var cs = new CryptoStream ( ms , encryptor , CryptoStreamMode . Write
))
15 {
16 using ( var sw = new StreamWriter ( cs ) )
17 {
18 sw . Write ( plainText ) ;
19 }
20 }
21 return Convert . ToBase64String ( ms . ToArray () ) ;
22 }
23 }
24 }

673
15.1. General Code Security and Best Practices in .NET

What is the role of HTTPS in securing communication in .NET applica-


tions?
HTTPS encrypts the data transmitted between the client and server, ensuring confidentiality and
integrity of the data in transit. In .NET applications, it’s crucial to configure the server to enforce
HTTPS, redirect HTTP requests to HTTPS, and use SSL/TLS certificates.

Listing 15.6: Code example

1 // Example : Enforcing HTTPS in ASP . NET Core


2 public class Startup
3 {
4 public void Configure ( IApplicationBuilder app )
5 {
6 app . UseHttpsRedirection () ;
7 // Other middleware
8 }
9 }

How can you securely manage secrets and sensitive configuration data in
.NET?
Secrets such as API keys, database connection strings, and passwords should never be hardcoded.
Use secret management tools like Azure Key Vault, AWS Secrets Manager, or environment vari-
ables to securely store and access secrets in .NET applications.

Listing 15.7: Code example

1 // Example : Accessing secrets from Azure Key Vault in ASP . NET Core
2 public class Startup
3 {
4 public void ConfigureServices ( IServiceCollection services )
5 {
6 var keyVaultEndpoint = new Uri ( Environment . GetEnvironmentVariable ( "
KEY_VAULT_ENDPOINT " ) ) ;
7 var azureKeyVaultClient = new DefaultAzureCredential () ;
8 services . AddAzureKeyVault ( keyVaultEndpoint , azureKeyVaultClient ) ;
9 }
10 }

What are secure serialization practices in .NET?


When serializing data in .NET, avoid deserializing untrusted data directly. Always validate and
sanitize inputs before deserializing, and avoid using insecure serializers like ‘BinaryFormatter‘,

674
15.1. General Code Security and Best Practices in .NET

which are vulnerable to deserialization attacks. Use safe serializers like ‘JsonSerializer‘ or ‘Data-
ContractSerializer‘.
Listing 15.8: Code example

1 // Example : Secure serialization using System . Text . Json


2 public string SerializeObject ( object obj )
3 {
4 return JsonSerializer . Serialize ( obj ) ;
5 }
6

7 public T DeserializeObject <T >( string json )


8 {
9 return JsonSerializer . Deserialize <T >( json ) ;
10 }

How do you implement role-based access control (RBAC) in a .NET


application?
Role-Based Access Control (RBAC) restricts access to resources based on the user’s role. In .NET,
you can use the ‘Authorize‘ attribute to enforce role-based restrictions on controllers or actions,
and manage user roles using the ASP.NET Identity framework.
Listing 15.9: Code example

1 // Example : Enforcing RBAC in ASP . NET Core


2 [ Authorize ( Roles = " Admin " ) ]
3 public IActionResult AdminDashboard ()
4 {
5 return View () ;
6 }

What is the purpose of CSRF protection in .NET applications, and how


is it implemented?
Cross-Site Request Forgery (CSRF) attacks trick users into executing unwanted actions on a web-
site where they are authenticated. In .NET, CSRF protection is implemented using anti-forgery
tokens that ensure that requests come from authenticated sources.
Listing 15.10: Code example

1 // Example : CSRF protection in ASP . NET Core


2 < form asp - action = " Submit " method = " post " >
3 @Html . AntiForgeryToken ()
4 < button type = " submit " > Submit </ button >
5 </ form >

675
15.1. General Code Security and Best Practices in .NET

How do you protect against open redirect vulnerabilities in .NET appli-


cations?
To protect against open redirect vulnerabilities, ensure that redirects are only made to trusted,
validated URLs. Always validate the ‘ReturnUrl‘ parameter and restrict redirects to whitelisted
domains.

Listing 15.11: Code example

1 // Example : Validating the ReturnUrl parameter in a redirect


2 public IActionResult Login ( string returnUrl = null )
3 {
4 if (! Url . IsLocalUrl ( returnUrl ) )
5 {
6 returnUrl = " / " ;
7 }
8 return Redirect ( returnUrl ) ;
9 }

How can you protect against brute force attacks in .NET applications?
To protect against brute force attacks, implement rate limiting, account lockouts, or CAPTCHA
after a number of failed login attempts. Additionally, ensure passwords are hashed and that
multi-factor authentication (MFA) is available.

Listing 15.12: Code example

1 // Example : Implementing account lockout in ASP . NET Core Identity


2 services . Configure < IdentityOptions >( options = >
3 {
4 options . Lockout . DefaultLockoutTimeSpan = TimeSpan . FromMinutes (5) ;
5 options . Lockout . MaxFailedAccessAttempts = 5;
6 options . Lockout . AllowedForNewUsers = true ;
7 }) ;

What are common practices for logging security events in .NET?


Security events such as failed login attempts, privilege escalations, and suspicious activity should
be logged. Use centralized logging with frameworks like ‘Serilog‘ or ‘NLog‘, and ensure logs are
protected from unauthorized access or tampering.

Listing 15.13: Code example

1 // Example : Logging security events with Serilog in ASP . NET Core


2 public void Configure ( IApplicationBuilder app , ILogger < Startup > logger )
3 {

676
15.1. General Code Security and Best Practices in .NET

4 logger . LogInformation ( " Security event : User login attempt " ) ;


5 app . UseSerilogRequestLogging () ;
6 }

How do you securely handle file uploads in .NET?


When handling file uploads, validate the file type, size, and contents to prevent malicious files
from being uploaded. Store files in a secure location and avoid executing or trusting uploaded files
without proper sanitization.

Listing 15.14: Code example

1 // Example : Validating file uploads in ASP . NET Core


2 public async Task < IActionResult > Upload ( IFormFile file )
3 {
4 if ( file . Length > 0 && Path . GetExtension ( file . FileName ) == " . jpg " )
5 {
6 var filePath = Path . Combine ( uploadsFolder , file . FileName ) ;
7 using ( var stream = System . IO . File . Create ( filePath ) )
8 {
9 await file . CopyToAsync ( stream ) ;
10 }
11 }
12 return Ok () ;
13 }

How can you prevent directory traversal attacks in .NET applications?


To prevent directory traversal attacks, ensure that file paths are validated and sanitized. Do not
directly concatenate user inputs into file paths, and ensure that path traversal characters like ‘..‘
are not allowed.

Listing 15.15: Code example

1 // Example : Preventing directory traversal by validating file paths


2 public string GetFilePath ( string fileName )
3 {
4 var safeFileName = Path . GetFileName ( fileName ) ; // Strip dangerous characters
5 return Path . Combine ( baseDirectory , safeFileName ) ;
6 }

677
15.1. General Code Security and Best Practices in .NET

How do you ensure proper error handling and prevent information dis-
closure in .NET?
Error messages should not reveal sensitive information such as stack traces or database details
in production environments. Use global exception handling to log errors internally and display
user-friendly error messages.

Listing 15.16: Code example

1 // Example : Global exception handling in ASP . NET Core


2 public void Configure ( IApplicationBuilder app , IHostingEnvironment env )
3 {
4 if ( env . IsDevelopment () )
5 {
6 app . UseDeveloperExceptionPage () ;
7 }
8 else
9 {
10 app . UseExceptionHandler ( " / Error " ) ;
11 }
12 }

How can you enforce strong password policies in .NET applications?


Enforce strong password policies by requiring a minimum length, a combination of upper/lowercase
letters, numbers, and special characters. ASP.NET Identity supports configuring password policies
out of the box.

Listing 15.17: Code example

1 // Example : Enforcing strong password policies in ASP . NET Core Identity


2 services . Configure < IdentityOptions >( options = >
3 {
4 options . Password . RequireDigit = true ;
5 options . Password . RequiredLength = 8;
6 options . Password . RequireNonAlphanumeric = true ;
7 options . Password . RequireUppercase = true ;
8 options . Password . RequireLowercase = true ;
9 }) ;

How do you prevent session hijacking in .NET applications?


To prevent session hijacking, always use HTTPS for secure transmission of session cookies, set
the ‘HttpOnly‘ and ‘Secure‘ flags on cookies, and consider using sliding expiration to limit session
lifespan.

678
15.1. General Code Security and Best Practices in .NET

Listing 15.18: Code example

1 // Example : Configuring secure session cookies in ASP . NET Core


2 services . AddSession ( options = >
3 {
4 options . Cookie . HttpOnly = true ;
5 options . Cookie . SecurePolicy = CookieSecurePolicy . Always ;
6 options . IdleTimeout = TimeSpan . FromMinutes (20) ;
7 }) ;

How do you implement secure API authentication in .NET using OAuth2


and JWT?
OAuth2 and JWT (JSON Web Tokens) are commonly used to secure APIs in .NET. OAuth2
provides token-based authentication, while JWT ensures that API requests are authenticated using
signed tokens that are passed in the ‘Authorization‘ header.

Listing 15.19: Code example

1 // Example : Securing an API using JWT authentication in ASP . NET Core


2 services . AddAuthentication ( JwtBearerDefaults . AuthenticationScheme )
3 . AddJwtBearer ( options = >
4 {
5 options . TokenValidationParameters = new TokenValidationParameters
6 {
7 ValidateIssuer = true ,
8 ValidateAudience = true ,
9 ValidateIssuerSigningKey = true ,
10 ValidIssuer = " yourissuer . com " ,
11 ValidAudience = " youraudience . com " ,
12 IssuerSigningKey = new SymmetricSecurityKey ( Encoding . UTF8 . GetBytes ( "
YourSecretKey " ) )
13 };
14 }) ;

What is the principle of least privilege, and how can it be applied in


.NET?
The principle of least privilege ensures that users, services, and applications are granted only the
permissions necessary to perform their functions. In .NET, you can apply this principle by ensuring
that roles and permissions are granular, and by assigning the minimal necessary privileges to users
and services.

Listing 15.20: Code example

1 // Example : Applying least privilege by restricting user roles in ASP . NET Core

679
15.2. Obfuscation, Data Protection, Authentication, and Authorization

2 [ Authorize ( Roles = " Admin , Manager " ) ]


3 public IActionResult ManageUsers ()
4 {
5 return View () ;
6 }

15.2 Obfuscation, Data Protection, Authentication, and Au-


thorization
What is code obfuscation, and why is it important in .NET applications?
Code obfuscation is the process of transforming readable code into a more complex and confusing
form to make it difficult for attackers to reverse-engineer or understand the logic. It is used to
protect intellectual property, prevent tampering, and increase security in .NET applications.

Listing 15.21: Code example

1 // Example before obfuscation


2 public class SensitiveService
3 {
4 public string GetSecret ()
5 {
6 return " ThisIsASecret " ;
7 }
8 }
9

10 // After obfuscation , the method names and logic may be scrambled , making it
harder to understand .

How does obfuscation impact performance, and what should developers


be aware of when using it?
Obfuscation generally has minimal impact on runtime performance since it mainly alters the source-
level representation of the code rather than its compiled functionality. However, it can make
debugging and profiling more difficult. Developers should ensure that obfuscation does not interfere
with debugging or logging in production environments.

Listing 15.22: Code example

1 // A method in non - obfuscated form


2 public int CalculateSum ( int a , int b )
3 {
4 return a + b ;
5 }

680
15.2. Obfuscation, Data Protection, Authentication, and Authorization

7 // In obfuscated form , the names might be mangled , but the functionality remains
intact .

What is data protection, and how does .NET’s Data Protection API
work?
Data protection in .NET involves securing sensitive data, such as passwords, tokens, or keys, to
prevent unauthorized access. The Data Protection API (DPAPI) in .NET provides a cryptographic
framework for protecting sensitive data, using system-provided or custom keys for encryption and
decryption.

Listing 15.23: Code example

1 // Example : Using Data Protection API in . NET Core


2 public void ConfigureServices ( IServiceCollection services )
3 {
4 services . AddDataProtection ()
5 . PersistKeysToFileSystem ( new DirectoryInfo ( @ " c :\ mykeys \") )
6 . SetApplicationName ( " MyApp " ) ;
7 }

How does .NET Core handle encryption and decryption using the Data
Protection API?
.NET Core’s Data Protection API provides built-in services to encrypt and decrypt sensitive data.
It uses keys to encrypt data, which are persisted securely. Decryption requires the same key that
was used to encrypt the data.

Listing 15.24: Code example

1 // Example : Encrypting and decrypting data


2 public class MyService
3 {
4 private readonly IDataProtector _protector ;
5

6 public MyService ( IDataProtectionProvider provider )


7 {
8 _protector = provider . CreateProtector ( " MySecretPurpose " ) ;
9 }
10

11 public string ProtectData ( string input )


12 {
13 return _protector . Protect ( input ) ; // Encrypt the data
14 }

681
15.2. Obfuscation, Data Protection, Authentication, and Authorization

15

16 public string UnprotectData ( string input )


17 {
18 return _protector . Unprotect ( input ) ; // Decrypt the data
19 }
20 }

What is the difference between authentication and authorization in .NET


applications?
Authentication is the process of verifying a user’s identity (e.g., through a username and password).
Authorization, on the other hand, is the process of determining whether an authenticated user has
the right to access a resource or perform an action.

Listing 15.25: Code example

1 // Example : Authentication setup in ASP . NET Core


2 public void ConfigureServices ( IServiceCollection services )
3 {
4 services . AddAuthentication ( CookieAuthenticationDefaults . AuthenticationScheme )
5 . AddCookie ( options = >
6 {
7 options . LoginPath = " / Account / Login " ;
8 }) ;
9 }
10

11 // Example : Authorization setup


12 [ Authorize ( Roles = " Admin " ) ]
13 public IActionResult AdminPanel ()
14 {
15 return View () ;
16 }

How does role-based access control (RBAC) work in .NET, and how is
it implemented using ASP.NET Identity?
Role-Based Access Control (RBAC) allows you to assign users to roles and grant permissions
based on those roles. ASP.NET Identity provides role management features, allowing developers
to enforce access control using role attributes.

Listing 15.26: Code example

1 // Example : Using RBAC in ASP . NET Core


2 [ Authorize ( Roles = " Admin " ) ]
3 public IActionResult AdminDashboard ()

682
15.2. Obfuscation, Data Protection, Authentication, and Authorization

4 {
5 return View () ;
6 }
7

8 // User can also have multiple roles , e . g . , Admin , Manager


9 [ Authorize ( Roles = " Admin , Manager " ) ]
10 public IActionResult ManageUsers ()
11 {
12 return View () ;
13 }

What is JWT (JSON Web Token) authentication, and how does it work
in .NET applications?
JWT authentication is a stateless authentication mechanism where a signed token is issued after a
user successfully authenticates. The token contains claims and is sent with every request, allowing
the server to verify the user’s identity without storing session information.

Listing 15.27: Code example

1 // Example : JWT authentication setup in ASP . NET Core


2 public void ConfigureServices ( IServiceCollection services )
3 {
4 services . AddAuthentication ( JwtBearerDefaults . AuthenticationScheme )
5 . AddJwtBearer ( options = >
6 {
7 options . TokenValidationParameters = new TokenValidationParameters
8 {
9 ValidateIssuer = true ,
10 ValidateAudience = true ,
11 ValidateIssuerSigningKey = true ,
12 ValidIssuer = " yourIssuer " ,
13 ValidAudience = " yourAudience " ,
14 IssuerSigningKey = new SymmetricSecurityKey ( Encoding . UTF8 . GetBytes
( " yourSecretKey " ) )
15 };
16 }) ;
17 }

How can you generate and validate JWT tokens in .NET Core?
In .NET Core, JWT tokens are generated by creating claims that describe the user and signing
the token with a secret key. The token is validated by checking its signature, expiration, and issuer
when the user makes a request.

683
15.2. Obfuscation, Data Protection, Authentication, and Authorization

Listing 15.28: Code example

1 // Example : Generating a JWT token


2 public string GenerateJwtToken ( string username , string secretKey )
3 {
4 var claims = new []
5 {
6 new Claim ( JwtRegisteredClaimNames . Sub , username ) ,
7 new Claim ( JwtRegisteredClaimNames . Jti , Guid . NewGuid () . ToString () )
8 };
9

10 var key = new SymmetricSecurityKey ( Encoding . UTF8 . GetBytes ( secretKey ) ) ;


11 var creds = new SigningCredentials ( key , SecurityAlgorithms . HmacSha256 ) ;
12

13 var token = new JwtSecurityToken (


14 issuer : " yourIssuer " ,
15 audience : " yourAudience " ,
16 claims : claims ,
17 expires : DateTime . Now . AddMinutes (30) ,
18 signingCredentials : creds ) ;
19

20 return new JwtSecurityTokenHandler () . WriteToken ( token ) ;


21 }
22

23 // Example : Validating a JWT token


24 public ClaimsPrincipal ValidateJwtToken ( string token , string secretKey )
25 {
26 var tokenHandler = new JwtSecurityTokenHandler () ;
27 var key = Encoding . ASCII . GetBytes ( secretKey ) ;
28

29 var validationParams = new TokenValidationParameters


30 {
31 ValidateIssuerSigningKey = true ,
32 IssuerSigningKey = new SymmetricSecurityKey ( key ) ,
33 ValidateIssuer = true ,
34 ValidateAudience = true ,
35 ValidIssuer = " yourIssuer " ,
36 ValidAudience = " yourAudience " ,
37 ClockSkew = TimeSpan . Zero
38 };
39

40 return tokenHandler . ValidateToken ( token , validationParams , out SecurityToken


validatedToken ) ;
41 }

684
15.2. Obfuscation, Data Protection, Authentication, and Authorization

How does multi-factor authentication (MFA) work in .NET applications?


Multi-factor authentication (MFA) requires users to provide additional verification beyond just a
username and password, such as a code sent via SMS or generated by an authenticator app. In
ASP.NET Identity, MFA can be implemented by requiring a second step of verification after the
primary authentication.

Listing 15.29: Code example

1 // Example : Enforcing two - factor authentication in ASP . NET Core Identity


2 [ Authorize ]
3 public async Task < IActionResult > TwoFactorAuthentication ()
4 {
5 var user = await _userManager . GetUserAsync ( User ) ;
6 var code = await _userManager . GenerateTwoFactorTokenAsync ( user , " Email " ) ;
7

8 // Send code via email or SMS


9 await _emailSender . SendEmailAsync ( user . Email , " Your authentication code " , $ "
Your code is { code } " ) ;
10

11 return View () ;
12 }

What are anti-forgery tokens, and how do they help in preventing CSRF
attacks in .NET applications?
Anti-forgery tokens help prevent Cross-Site Request Forgery (CSRF) attacks by ensuring that
requests come from authenticated and trusted sources. In .NET, these tokens are automatically
generated and verified for form submissions.

Listing 15.30: Code example

1 // Example : Anti - forgery token usage in Razor views


2 < form method = " post " asp - action = " SubmitData " >
3 @Html . AntiForgeryToken ()
4 < input type = " submit " value = " Submit " / >
5 </ form >
6

7 // Example : Validating anti - forgery tokens in controllers


8 [ HttpPost ]
9 [ ValidateAntiForgeryToken ]
10 public IActionResult SubmitData ()
11 {
12 // Handle form submission
13 return Ok () ;
14 }

685
15.2. Obfuscation, Data Protection, Authentication, and Authorization

How can you implement claims-based authentication in .NET Core?


Claims-based authentication allows you to authenticate users based on a set of claims (key-value
pairs) that describe the user’s identity and roles. In .NET Core, claims are added to the authen-
tication token and can be used to authorize users.

Listing 15.31: Code example

1 // Example : Adding claims in JWT token generation


2 var claims = new []
3 {
4 new Claim ( ClaimTypes . Name , " username " ) ,
5 new Claim ( ClaimTypes . Role , " Admin " )
6 };
7

8 var token = new JwtSecurityToken (


9 issuer : " yourIssuer " ,
10 audience : " yourAudience " ,
11 claims : claims ,
12 expires : DateTime . UtcNow . AddHours (1) ,
13 signingCredentials : new SigningCredentials ( new SymmetricSecurityKey ( Encoding .
UTF8 . GetBytes ( " yourSecretKey " ) ) , SecurityAlgorithms . HmacSha256 )
14 );

How can you use OAuth 2.0 for authentication and authorization in .NET
Core?
OAuth 2.0 is an authorization framework that allows third-party applications to access a user’s
resources without exposing credentials. In .NET Core, you can use OAuth 2.0 to authenticate
users via external providers like Google or Facebook.

Listing 15.32: Code example

1 // Example : Configuring OAuth 2.0 authentication with Google in ASP . NET Core
2 public void ConfigureServices ( IServiceCollection services )
3 {
4 services . AddAuthentication ( options = >
5 {
6 options . DefaultAuthenticateScheme = CookieAuthenticationDefaults .
AuthenticationScheme ;
7 options . DefaultChallengeScheme = GoogleDefaults . AuthenticationScheme ;
8 })
9 . AddCookie ()
10 . AddGoogle ( options = >
11 {
12 options . ClientId = " yourClientId " ;
13 options . ClientSecret = " yourClientSecret " ;

686
15.2. Obfuscation, Data Protection, Authentication, and Authorization

14 }) ;
15 }

How do you implement token expiration and refresh tokens in .NET


Core?
Token expiration ensures that access tokens are only valid for a limited time, while refresh tokens
allow clients to obtain new access tokens without requiring user re-authentication. In .NET Core,
you can generate both access and refresh tokens for secure, long-term sessions.

Listing 15.33: Code example

1 // Example : Generating an access and refresh token


2 public ( string AccessToken , string RefreshToken ) GenerateTokens ( string username )
3 {
4 var accessToken = new JwtSecurityToken (
5 issuer : " yourIssuer " ,
6 audience : " yourAudience " ,
7 expires : DateTime . UtcNow . AddMinutes (30) ,
8 signingCredentials : new SigningCredentials ( new SymmetricSecurityKey (
Encoding . UTF8 . GetBytes ( " yourSecretKey " ) ) , SecurityAlgorithms . HmacSha256
)
9 );
10

11 var refreshToken = Guid . NewGuid () . ToString () ; // Simple refresh token


generation
12 return ( new JwtSecurityTokenHandler () . WriteToken ( accessToken ) , refreshToken ) ;
13 }

How do you protect sensitive data like connection strings and API keys
in .NET applications?
Sensitive data like connection strings and API keys should not be hardcoded in the application.
Instead, use secure storage mechanisms such as environment variables, Azure Key Vault, or appset-
tings.json with encrypted values.

Listing 15.34: Code example

1 // Example : Storing and retrieving API keys from Azure Key Vault
2 public void ConfigureServices ( IServiceCollection services )
3 {
4 var keyVaultUri = new Uri ( Environment . GetEnvironmentVariable ( " KEY_VAULT_URI " ) )
;
5 var credential = new DefaultAzureCredential () ;
6

687
15.2. Obfuscation, Data Protection, Authentication, and Authorization

7 services . AddAzureKeyVault ( keyVaultUri , credential ) ;


8 }

What is the principle of least privilege, and how can you enforce it in
.NET applications?
The principle of least privilege ensures that users, applications, and services are granted only the
minimum necessary permissions. In .NET, this can be enforced by applying granular roles and
permissions and ensuring sensitive operations are restricted to authorized users.

Listing 15.35: Code example

1 // Example : Enforcing least privilege by restricting access to sensitive methods


2 [ Authorize ( Roles = " Admin " ) ]
3 public IActionResult DeleteUserAccount ( int userId )
4 {
5 // Logic for deleting user accounts
6 return Ok () ;
7 }

How do you implement secure password hashing in .NET?


Secure password hashing involves using strong algorithms like PBKDF2, bcrypt, or Argon2, which
are designed to resist brute-force attacks. ASP.NET Identity automatically hashes passwords using
PBKDF2 with a salt.

Listing 15.36: Code example

1 // Example : Hashing a password manually using PBKDF2


2 public string HashPassword ( string password )
3 {
4 using ( var rfc2898DeriveBytes = new Rfc2898DeriveBytes ( password , 16 , 10000) )
5 {
6 var salt = rfc2898DeriveBytes . Salt ;
7 var hash = rfc2898DeriveBytes . GetBytes (32) ;
8 return Convert . ToBase64String ( salt ) + " : " + Convert . ToBase64String ( hash ) ;
9 }
10 }

How do you handle session management securely in .NET applications?


In .NET, session management should be done securely by using ‘Secure‘ and ‘HttpOnly‘ flags on
cookies, enabling sliding expiration, and ensuring that session tokens are encrypted and transmitted
over HTTPS.

688
15.3. Network Security, Preventing Common Security Vulnerabilities

Listing 15.37: Code example

1 // Example : Secure session management in ASP . NET Core


2 public void ConfigureServices ( IServiceCollection services )
3 {
4 services . AddSession ( options = >
5 {
6 options . Cookie . HttpOnly = true ;
7 options . Cookie . SecurePolicy = CookieSecurePolicy . Always ;
8 options . IdleTimeout = TimeSpan . FromMinutes (20) ;
9 }) ;
10 }

15.3 Network Security, Preventing Common Security Vul-


nerabilities
What is the role of HTTPS in securing network communication in .NET
applications?
HTTPS ensures secure communication over the network by encrypting data between the client and
server, protecting it from eavesdropping and tampering. In .NET applications, HTTPS is enforced
by configuring SSL/TLS certificates and redirecting HTTP requests to HTTPS.

Listing 15.38: Code example

1 // Example : Enforcing HTTPS in ASP . NET Core


2 public class Startup
3 {
4 public void Configure ( IApplicationBuilder app )
5 {
6 app . UseHttpsRedirection () ; // Redirect HTTP requests to HTTPS
7 }
8 }

What is the impact of improper certificate validation on network secu-


rity?
Improper certificate validation can expose applications to man-in-the-middle (MITM) attacks,
where attackers can intercept and alter communication between the client and server. Always
ensure that SSL/TLS certificates are properly validated.

Listing 15.39: Code example

1 // Example : Enforcing strict certificate validation in HttpClient

689
15.3. Network Security, Preventing Common Security Vulnerabilities

2 var handler = new HttpClientHandler


3 {
4 ServerCertificateCustomValidationCallback = ( message , cert , chain , errors ) = >
5 {
6 return errors == SslPolicyErrors . None ; // Only accept valid certificates
7 }
8 };
9

10 var client = new HttpClient ( handler ) ;

How does DNS spoofing work, and how can it be mitigated in .NET
applications?
DNS spoofing involves intercepting and altering DNS queries to redirect users to malicious websites.
To mitigate DNS spoofing, use DNSSEC (DNS Security Extensions) and ensure that domain
lookups and network traffic are encrypted using HTTPS.
Listing 15.40: Code example

1 // Example : DNS lookup in . NET with hostname validation


2 public async Task < string > GetHostIpAsync ( string hostname )
3 {
4 IPHostEntry host = await Dns . GetHostEntryAsync ( hostname ) ;
5 return host . AddressList . First () . ToString () ;
6 }

What is cross-site scripting (XSS), and how can you prevent it in web
applications?
XSS is a vulnerability where attackers inject malicious scripts into web pages that are executed by
unsuspecting users. To prevent XSS in web applications, always encode user inputs when rendering
them on the page and avoid directly embedding untrusted data into HTML, JavaScript, or URLs.
Listing 15.41: Code example

1 // Example : Preventing XSS in Razor views ( ASP . NET Core )


2 @Html . Encode ( Model . UserInput )

What is a man-in-the-middle (MITM) attack, and how can it be pre-


vented in .NET applications?
A man-in-the-middle (MITM) attack occurs when an attacker intercepts communication between
the client and server, potentially altering the data. To prevent MITM attacks, always use HTTPS
for communication, validate SSL/TLS certificates, and avoid insecure ciphers.

690
15.3. Network Security, Preventing Common Security Vulnerabilities

Listing 15.42: Code example

1 // Example : Enforcing HTTPS and secure communication


2 services . AddHsts ( options = >
3 {
4 options . Preload = true ;
5 options . IncludeSubDomains = true ;
6 options . MaxAge = TimeSpan . FromDays (365) ;
7 }) ;

How does SQL injection work, and what are the best practices to prevent
it in .NET applications?
SQL injection occurs when attackers insert malicious SQL queries into input fields, manipulating
the database. Prevent SQL injection by using parameterized queries and avoiding direct concate-
nation of user inputs in SQL statements.

Listing 15.43: Code example

1 // Example : Preventing SQL injection using parameterized queries in ADO . NET


2 public void GetUserById ( int userId )
3 {
4 using ( var connection = new SqlConnection ( connectionString ) )
5 {
6 var command = new SqlCommand ( " SELECT * FROM Users WHERE Id = @userId " ,
connection ) ;
7 command . Parameters . AddWithValue ( " @userId " , userId ) ;
8

9 connection . Open () ;
10 var reader = command . ExecuteReader () ;
11 // Process results
12 }
13 }

What is cross-site request forgery (CSRF), and how do you prevent it in


.NET web applications?
CSRF is a type of attack where an attacker tricks a user into submitting unauthorized requests
to a trusted website where they are authenticated. To prevent CSRF, use anti-forgery tokens in
forms and validate these tokens server-side before processing requests.

Listing 15.44: Code example

1 // Example : Anti - forgery token usage in Razor views


2 < form asp - action = " SubmitForm " method = " post " >
3 @Html . AntiForgeryToken ()

691
15.3. Network Security, Preventing Common Security Vulnerabilities

4 < button type = " submit " > Submit </ button >
5 </ form >
6

7 // Example : Validating the anti - forgery token in the controller


8 [ HttpPost ]
9 [ ValidateAntiForgeryToken ]
10 public IActionResult SubmitForm ()
11 {
12 // Handle form submission
13 return Ok () ;
14 }

How do you prevent insecure deserialization vulnerabilities in .NET ap-


plications?
Insecure deserialization occurs when untrusted data is deserialized without proper validation, po-
tentially leading to code execution. Prevent it by avoiding deserialization of untrusted data, using
secure serializers like ‘JsonSerializer‘, and validating all inputs before deserialization.

Listing 15.45: Code example

1 // Example : Secure deserialization using System . Text . Json


2 public T DeserializeObject <T >( string jsonData )
3 {
4 return JsonSerializer . Deserialize <T >( jsonData ) ;
5 }

What is a distributed denial-of-service (DDoS) attack, and how can you


mitigate it in .NET applications?
A DDoS attack overwhelms a server with traffic, rendering it unavailable. To mitigate DDoS
attacks, use rate limiting, load balancing, and services like Azure DDoS Protection to manage
traffic and block malicious requests.

Listing 15.46: Code example

1 // Example : Implementing rate limiting to mitigate DDoS in ASP . NET Core


2 public void ConfigureServices ( IServiceCollection services )
3 {
4 services . AddRateLimiter ( options = >
5 {
6 options . GlobalRateLimit = new RateLimitOptions
7 {
8 MaxRequests = 100 ,
9 TimeWindow = TimeSpan . FromMinutes (1)

692
15.3. Network Security, Preventing Common Security Vulnerabilities

10 };
11 }) ;
12 }

How does clickjacking work, and how can it be prevented in .NET appli-
cations?
Clickjacking is an attack where malicious users trick victims into clicking hidden UI elements
(e.g., buttons, links) by overlaying them on deceptive pages. To prevent clickjacking, use the
‘X-Frame-Options‘ header to deny embedding your website in iframes.

Listing 15.47: Code example

1 // Example : Adding X - Frame - Options header in ASP . NET Core to prevent clickjacking
2 public void Configure ( IApplicationBuilder app )
3 {
4 app . Use ( async ( context , next ) = >
5 {
6 context . Response . Headers . Add ( "X - Frame - Options " , " DENY " ) ;
7 await next () ;
8 }) ;
9 }

How can you secure API endpoints in .NET applications using OAuth2?
OAuth2 allows secure access to APIs by issuing access tokens that authenticate users without
exposing their credentials. In .NET, use OAuth2 to secure API endpoints by validating access
tokens and enforcing role-based access control (RBAC).

Listing 15.48: Code example

1 // Example : Securing API endpoint with OAuth2 in ASP . NET Core


2 [ Authorize ( AuthenticationSchemes = JwtBearerDefaults . AuthenticationScheme ) ]
3 [ HttpGet ( " secure - api " ) ]
4 public IActionResult GetSecureData ()
5 {
6 return Ok ( " This is secured data " ) ;
7 }

What is network sniffing, and how can you protect against it in .NET
applications?
Network sniffing is the unauthorized interception of data traveling over a network. To protect
against it, always use HTTPS to encrypt communication, validate certificates, and avoid trans-

693
15.3. Network Security, Preventing Common Security Vulnerabilities

mitting sensitive data in plain text.

Listing 15.49: Code example

1 // Example : Enforcing HTTPS to protect against network sniffing


2 public void Configure ( IApplicationBuilder app )
3 {
4 app . UseHttpsRedirection () ;
5 }

How do you implement HSTS (HTTP Strict Transport Security) in .NET


Core to improve security?
HSTS forces browsers to only communicate with the server over HTTPS, preventing downgrade
attacks and man-in-the-middle attacks. In .NET Core, you can enable HSTS using middleware.

Listing 15.50: Code example

1 // Example : Enabling HSTS in ASP . NET Core


2 public void Configure ( IApplicationBuilder app , IWebHostEnvironment env )
3 {
4 if (! env . IsDevelopment () )
5 {
6 app . UseHsts () ; // Enable HSTS for production
7 }
8 }

How can you use content security policies (CSP) to prevent XSS attacks
in .NET applications?
A Content Security Policy (CSP) restricts the sources from which content (e.g., scripts, images)
can be loaded, reducing the risk of XSS attacks. You can configure CSP headers in .NET to allow
only trusted sources.

Listing 15.51: Code example

1 // Example : Adding Content - Security - Policy header in ASP . NET Core


2 public void Configure ( IApplicationBuilder app )
3 {
4 app . Use ( async ( context , next ) = >
5 {
6 context . Response . Headers . Add ( " Content - Security - Policy " , " default - src ’ self
’" ) ;
7 await next () ;
8 }) ;
9 }

694
15.3. Network Security, Preventing Common Security Vulnerabilities

What is network segmentation, and how does it improve network secu-


rity?
Network segmentation divides a network into smaller segments, reducing the attack surface by
isolating sensitive systems. If an attacker compromises one segment, they cannot easily move
laterally to other segments.

Listing 15.52: Code example

1 // No direct code example for network segmentation in . NET , but it can be


configured at the infrastructure level ( e . g . , firewalls , VLANs ) .

How do you use role-based access control (RBAC) to secure network


resources in .NET applications?
RBAC ensures that only authorized users can access specific network resources based on their
roles. In .NET, you can implement RBAC by enforcing roles in API endpoints or controllers using
the ‘Authorize‘ attribute.

Listing 15.53: Code example

1 // Example : Role - based access control in ASP . NET Core


2 [ Authorize ( Roles = " Admin " ) ]
3 public IActionResult AdminDashboard ()
4 {
5 return View () ;
6 }

How can you prevent brute-force login attacks in .NET applications?


Brute-force login attacks can be prevented by implementing account lockout after several failed
login attempts, using CAPTCHA, and rate-limiting login requests. ASP.NET Identity provides
built-in support for account lockouts.

Listing 15.54: Code example

1 // Example : Enabling account lockout in ASP . NET Core Identity


2 services . Configure < IdentityOptions >( options = >
3 {
4 options . Lockout . DefaultLockoutTimeSpan = TimeSpan . FromMinutes (5) ;
5 options . Lockout . MaxFailedAccessAttempts = 5;
6 options . Lockout . AllowedForNewUsers = true ;
7 }) ;

695
15.3. Network Security, Preventing Common Security Vulnerabilities

How do you securely store sensitive data like passwords in .NET appli-
cations?
Sensitive data such as passwords should never be stored in plain text. Instead, use strong hashing
algorithms like PBKDF2, bcrypt, or Argon2 to hash passwords. ASP.NET Identity automatically
hashes passwords using PBKDF2.

Listing 15.55: Code example

1 // Example : Hashing a password using ASP . NET Core Identity


2 public async Task < IActionResult > Register ( string password )
3 {
4 var user = new IdentityUser { UserName = " newUser " };
5 var result = await _userManager . CreateAsync ( user , password ) ;
6

7 if ( result . Succeeded )
8 {
9 return Ok ( " User registered " ) ;
10 }
11

12 return BadRequest ( " Registration failed " ) ;


13 }

How do you ensure secure API communication using API keys in .NET
applications?
API keys provide a simple way to authenticate API requests. Ensure that API keys are transmitted
over HTTPS, stored securely in environment variables, and rotated regularly. In .NET, API keys
can be checked in middleware or filters.

Listing 15.56: Code example

1 // Example : Securing API with API key validation middleware


2 public class ApiKeyMiddleware
3 {
4 private readonly RequestDelegate _next ;
5

6 public ApiKeyMiddleware ( RequestDelegate next )


7 {
8 _next = next ;
9 }
10

11 public async Task InvokeAsync ( HttpContext context )


12 {
13 if (! context . Request . Headers . TryGetValue ( "X - API - KEY " , out var
extractedApiKey ) )

696
15.3. Network Security, Preventing Common Security Vulnerabilities

14 {
15 context . Response . StatusCode = 401;
16 await context . Response . WriteAsync ( " API Key was not provided . " ) ;
17 return ;
18 }
19

20 if ( extractedApiKey != " your - api - key - here " )


21 {
22 context . Response . StatusCode = 403;
23 await context . Response . WriteAsync ( " Unauthorized client . " ) ;
24 return ;
25 }
26

27 await _next ( context ) ;


28 }
29 }

697

You might also like