Test Your Skills in C Programming - Vaskaran Sarcar
Test Your Skills in C Programming - Vaskaran Sarcar
This work is subject to copyright. All rights are solely and exclusively
licensed by the Publisher, whether the whole or part of the material is
concerned, specifically the rights of translation, reprinting, reuse of
illustrations, recitation, broadcasting, reproduction on microfilms or in any
other physical way, and transmission or information storage and retrieval,
electronic adaptation, computer software, or by similar or dissimilar
methodology now known or hereafter developed.
The publisher, the authors, and the editors are safe to assume that the advice
and information in this book are believed to be true and accurate at the date
of publication. Neither the publisher nor the authors or the editors give a
warranty, expressed or implied, with respect to the material contained
herein or for any errors or omissions that may have been made. The
publisher remains neutral with regard to jurisdictional claims in published
maps and institutional affiliations.
I do not know what I may appear to the world, but to myself I seem
to have been only like a boy playing on the seashore, and diverting
myself in now and then finding a smoother pebble or a prettier shell
than ordinary, whilst the great ocean of truth lay all undiscovered
before me.
You can find similar quotes from other great personalities too. For now,
let me give you one more example: Malcom Gladwell in his book Outliers
(Little, Brown, and Company) talked about the 10,000-hour rule. This rule
says that the key to achieving world-class expertise in any skill, is, to a
large extent, a matter of practicing the correct way, for a total of around
10,000 hours. So, even though we may claim that we know something very
well, we actually know very little. Learning is a continuous process, and
there is no end to it. This is why you can surely assume that if anyone
makes a 1,000-page book, that also may not be sufficient to cover all the
topics and features in C#.
But there is something called effective learning. It teaches you how to
learn fast to serve your need. Have you heard of the Pareto principle, or 80-
20 rule? This rule simply states that 80 percent of outcomes come from 20
percent of all causes. This is useful in programming too. When you learn
the fundamental and most important features of a programming language,
you will be confident in using it. Though you may not know every aspect of
a programming language, you will know the common constructs and widely
used features. This book is for those who acknowledge this fact. It will help
you to review your understanding of the core constructs and features of C#
using 15 chapters that have 430+ questions (170+ theoretical, and 260+
programming) and answers with lots of explanations. I tried to keep a
balance between the most recent features and commonly used features in
C#.
How Is the Book Organized?
The book has three major parts.
Part I contains the first three chapters, in which you will see a detailed
discussion on .NET fundamentals, useful data types in C#, and different
kinds of programming statements. These are the fundamental building
blocks for the rest of the book.
C# is a powerful object-oriented language. So, understanding the
concepts of classes, objects, inheritance, encapsulation, polymorphism,
and abstraction is essential for you. In Part II, you’ll see a detailed
discussion on each of these topics in Chapters 4 through 9; this is the
heart of this book. Once you understand them, you will be a better
programmer.
There is no end to learning. So, Part III includes some interesting topics
such as delegates, events, lambda expressions, generics, and
multithreading. These are advanced concepts, and particularly, the last
two of them are really big chapters. A quick overview of these topics will
help you make better applications. They will also make your foundation
strong for more advanced C# topics. The last chapter of the book
discusses a few important topics including the preview features of C# 11.
The best way to learn is by doing and analyzing case studies. So,
throughout this book, you will see interesting program segments, and I’ll
ask you to predict the output. To help you understand the code better, at
the beginning of each chapter you will see theoretical questions and
answers too.
Each theorical question is marked with <chapter_no>.T<Question_no>,
and each programming question is marked with
<chapter_no>.P<Question_no>. For example, 5.T3 means theoretical
question number 3 from Chapter 5, and 10.P2 means programming
question number 2 from Chapter 10.
You can download all the source code in the book from the publisher’s
website ( www.apress.com ). I plan to maintain the “Errata” list, and
if required, I can also make some updates/announcements there. So, I
suggest that you visit Apress.com periodically to receive any important
corrections or updates.
Prerequisite Knowledge
The target readers for this book are those who have completed a basic
tutorial, a book, or a course on C# but want to prepare themselves for an
examination or a job interview. I assume that you are familiar with the basic
language constructs in C# and have an idea about the pure object-oriented
concepts such as polymorphism, inheritance, abstraction, encapsulation,
and, most importantly, how to compile or run a C# application in Visual
Studio. This book does not invest time in easily available topics, such as
how to install Visual Studio on your system or how to write a “Hello
World” program in C#, and so forth. The code examples and questions and
answers (Q&As) are straightforward. I believe that by analyzing these
Q&As, you can evaluate your understanding of C#. These discussions will
make your future learning easier and enjoyable, but most importantly, they
will make you confident about using C#.
Who Is This Book For?
In short, you will get the most from this book if you can answer “yes” to the
following questions:
Are you familiar with basic constructs in C# and object-oriented concepts
such as polymorphism, inheritance, abstraction, and encapsulation?
Do you know how to set up your coding environment?
Have you completed at least one basic course on C# and now you are
interested in reviewing your understanding?
Are you interested in knowing how the core constructs of C# can help
you make useful applications?
You probably shouldn’t pick up this book if you can answer “yes” to
any of the following questions:
Are you looking for a C# tutorial or reference book?
Are you looking for advanced concepts in C# excluding the topics
mentioned previously?
Are you interested in exploring a book where the focus is not on the
Q&As?
Have you ever said, “I do not like Windows, Visual Studio, and/or .NET.
I want to learn and use C# without them.”
Useful Software
These are the important software and tools that I used for this book:
I started this book with the latest edition of C# (C# 10.0) using
Microsoft’s Visual Studio Community 2022 in a Windows 10
environment. During the process of writing, the C# 11 preview was
released. So, I kept updating the code to test C# 11 with .NET 7. When I
finished my initial draft, I had the updated edition of Visual Studio
(version 17.2.3). It was the latest edition at that time. Applying some
specific settings (discussed in Chapter 15), you can test some of those
preview features with this edition of Visual Studio too.
In C# programming, there are different channels. They target different
users. At a particular time, you may work on any of the following
channels: Preview channel, Current channel, Long-term support channel
(LTSC). The current channel was not sufficient to test all of the preview
features. So, to test some specific preview features, I used the preview
channel as well. For example, in 15.p3, to test the auto-default struct, I
used Visual Studio version 17.3.0 (Preview 1.1).
In this context, it is useful to know that nowadays the C# language
version is automatically selected based on your project’s target
framework so that you can always get the highest compatible version by
default. In the latest versions, Visual Studio doesn’t support the UI to
change the value, but you can change it by editing the .csproj file.
As per the new rules, C# 11 is supported only on .NET 7 and newer
versions. C# 10 is supported only on .NET 6 and newer versions. C# 9 is
supported only on .NET 5 and newer versions. C# 8.0 is supported only
on .NET Core 3.x and newer versions. If you are interested in the C#
language versioning, you can visit
https://docs.microsoft.com/en-
us/dotnet/csharp/language-reference/configure-
language-version .
The good news for you is that the community edition is free of cost. If
you do not use the Windows operating system, you can also use Visual
Studio Code, which is a source code editor developed by Microsoft to
support the Windows, Linux, and Mac operating systems. This
multiplatform IDE is free. At the time of this writing, Visual Studio 2022
for Mac is also available, but I did not test my code on it.
Figure I-1 Download link for Visual Studio 2022, Visual Studio for Mac, and Visual Studio Code
Note At the time of this writing, this link works fine, and the
information is correct. But the link and policies may change in the
future. The same comment applies to all the links in this book.
Final Words
You are a smart person, because you have chosen a programming language
that will assist you throughout your career.
Remember that you have just started on this journey. As you learn and
review these concepts, I suggest you write your code; only then will you
master this area. There is no shortcut for this. Euclid,the ancient Greek
mathematician is often considered as the father of geometry. Do you
remember his reply to the ruler? If not, let me remind you of his reply:
“There is no royal road to geometry.” The same concept applies to any kind
of learning. So, study and code; learn a new concept and code again. Do not
give up when you face challenges. They are the indicators that you are
growing better.
After reading this book, you will be confident about programming in
C#. Good luck.
Any source code or other supplementary material referenced by the author
in this book is available to readers on GitHub
(https://github.com/apress/test-your-skills-cs-programming). For more
detailed information, please visit www.apress.com/source-code.
Acknowledgments
First, I thank the Almighty. I sincerely believe that His blessings were the
reason I could complete this book. I also extend my deepest gratitude and
thanks to the following people:
Ratanlal Sarkar and Manikuntala Sarkar: My dear parents, thank
you for your blessings.
Indrani, my wife; Ambika, my daughter; Aryaman, my son:
Sweethearts, I love you all.
Sambaran, my brother: Thank you for your constant encouragement.
Carsten, the technical editor: I know that whenever I was in need, your
support was there. Thank you one more time.
Celestin, Smriti, Laura, Shrikant, Nirmal: Thanks for giving me
another opportunity to work with you and Apress.
Kim Wimpsett, Linthaa, Sivachandran, Pradap: Thank you for your
exceptional support to improve my work. I thank you all for your
extraordinary supports.
Table of Contents
Part I: Foundations
Chapter 1:Fundamentals of .NET and C#
Theoretical Concepts
Common .NET Terminology
Preliminary Concepts in C#
Programming Skills
Basic Programming Concepts
Selection Statements
Ternary Operator
Iteration Statements
Jump Statements
Use of the var Keyword
Chapter 2:Strings and Arrays
Theoretical Concepts
Strings in C#
The Use of Arrays in C#
Programming Skills
String Fundamentals
Array Fundamentals
Chapter 3:Enumeration and Structure Types
Theoretical Concepts
Enumerations in C#
Structures in C#
Limitations of Using Structures in C#
Programming Skills
The enum Type Fundamentals
Reviewing the Flags Enumeration
The struct Type Fundamentals
Testing the Default Value Expression
Discussion About a C# 11 Feature
Testing a Nondestructive Mutation
Part II: Object-Oriented Programming
Chapter 4:Classes and Objects
Theoretical Concepts
Class and Object
Constructor
Bonus Q&A
Programming Skills
Basic Concepts and Use of Constructors
Optional Parameters and Object Initializers
Visibility Control Basics
Nested Classes
Use of Instance Methods
Chapter 5:Inheritance
Theoretical Concepts
Basic Concepts
Types of Inheritance
Different Aspects of Inheritance
Programming Skills
Fundamentals
Analyzing the sealed Keyword
Method Overloading
Method Overriding
Method Hiding
Covariance and Contravariance
Chapter 6:Polymorphism
Theoretical Concepts
Polymorphism
Abstract Class
Interface
Programming Skills
Basic Concepts
Abstract Class Case Studies
Interface Case Studies
One C# 11 Feature
Chapter 7:Encapsulation Using Properties and Indexers
Theoretical Concepts
Creating a Property
Alternative Code to Create a Property
Creating an Indexer
Programming Skills
Basic Concepts
Virtual and Abstract Properties
The Usage of the init Keyword
Using Indexers
Properties and Indexers in Interfaces
Bonus Q&A
Chapter 8:Handling Exceptions
Theoretical Concepts
Basic Concepts
Exception Filters
Custom Exception
Programming Skills
Fundamentals
Using Multiple Catch Blocks
Using a General Catch Block
Throwing and Rethrowing an Exception
Filtering Exceptions
Chapter 9:Useful Concepts
Theoretical Concepts
Type Conversions
C# Types
Unsafe Code
Extension Methods
Constants in C#
Bonus Q&A
Programming Skills
Static Data
Using Extension Methods
Passing the Value-Type by Value
Passing the Value-Type by Reference
Using the is Operator
Using as Operator
A Method That Returns Multiple Values
Comparing the const and readonly Keywords
One C# 10 Feature
Part III: Advanced C#
Chapter 10:Delegates
Theoretical Concepts
Delegates in C#
Generic Delegates
Programming Skills
Custom Delegates
Multicast Delegates
Generic Delegates
Variance in Delegates
Delegate Compatibility
Chapter 11:Events
Theoretical Concepts
Understanding Events
Different Aspects of Using Events
Programming Skills
Using a Built-in Event
Passing Data with Events
Event Accessors
Interface Events
Bonus Questions
Chapter 12:Lambda Expressions
Theoretical Concepts
Understanding Lambdas
Types of Lambda Expressions
Restrictions
Programming Skills
Basic Concepts
Expression-Bodied Members
Local Variables in Lambda Expressions
Tuples in Lambda Expressions
Event Subscription
Static Lambda
Natural Type
Bonus Questions
Chapter 13:Generics
Theoretical Concepts
Motivation and Uses
Generic Methods
Restrictions
Bonus Questions
Programming Skills
Basic Concepts
Finding the Default Values
Implementing a Generic Interface
Understanding Generic Constraints
Covariance
Contravariance
Self-Referencing Generic Type
Testing Overloading and Overriding
Analyzing Static Data
Chapter 14:Threading
Theoretical Concepts
Threading Fundamentals
Thread Safety
Deadlock
ThreadPool Class
Bonus Q&A
Programming Skills
Basic Concepts
Understanding the Join() Method
Handling Parameterized Methods
Using Lambdas
Foreground vs.Background Threads
Understanding Thread Synchronization
Exploring Deadlocks
Using ThreadPool
Managed Thread Cancellation
Chapter 15:Miscellaneous
Theoretical Concepts
Concept of Preview
Operator Overloading
Partial Classes and Methods
Programming Skills
Preview Features
Operator Overloading
Partial Classes
Partial Methods
Appendix A:A Final Note
A Personal Appeal
Appendix B:Recommended Reading
Index
About the Author
Vaskaran Sarcar
obtained his master’s of engineering in
software engineering from Jadavpur
University, Kolkata (India), and his MCA
from Vidyasagar University, Midnapore
(India). He was a National Gate Scholar
(2007–2009) and has more than 12 years of
experience in education and the IT
industry. Vaskaran devoted his early years
(2005–2007) to the teaching profession at
various engineering colleges, and later he
joined HP India PPS R&D Hub Bangalore.
He worked there until August 2019. At the
time of his retirement from HP, he was a
senior software engineer and team lead. To
follow his dream of writing books,
Vaskaran is now a full-time author. These are the other Apress books he has
written:
Java Design Patterns, Third Edition (Apress, 2022)
Simple and Efficient Programming in C# (Apress, 2021)
Design Patterns in C#, Second Edition (Apress, 2020)
Getting Started with Advanced C# (Apress, 2020)
Interactive Object-Oriented Programming in Java, Second Edition
(Apress, 2019)
Java Design Patterns, Second Edition (Apress, 2019)
Design Patterns in C# (Apress, 2018)
Interactive C# (Apress, 2017)
Interactive Object-Oriented Programming in Java (Apress, 2016)
Java Design Patterns (Apress, 2016)
The following are his non-Apress books:
Python Bookcamp (Amazon, 2021)
Operating System: Computer Science Interview Series (Createspace,
2014)
About the Technical Reviewer
Carsten Thomsen
is a back-end developer primarily but
works with smaller front-end bits as well.
He has authored and reviewed a number of
books and created numerous Microsoft
Learning courses, all to do with software
development. He works as a
freelancer/contractor in various countries in
Europe; Azure, Visual Studio, Azure
DevOps, and GitHub are some of the tools
he works with regularly. He is an
exceptional troubleshooter, and he also
enjoys working with architecture, research,
analysis, development, testing, and bug
fixing. Carsten is a great communicator
with mentoring and team-lead skills, and he
also enjoys researching and presenting new
material.
OceanofPDF.com
Part I
Foundations
Foundations
Part I consists of three chapters.
Chapter 1 focuses on the most important concepts in .NET and the
fundamentals of C#. It discusses the basic programming constructs in C#,
useful operators, explicit casting, selection statements with case guards,
iteration statements, jump statements, and so on.
Chapter 2 covers various aspects of using strings and arrays in C#.
Chapter 3 covers various aspects of using structures and enumerations in
C#.
Since its inception in 2002, C# has continued to evolve and introduce
new features to meet developers’ expectations. This book is not meant to
cover every aspect of C#; instead, it reviews the recently-added and most
commonly used features in C#, so that you can be confident about C# and
prepare quickly before an examination or a job interview. Most importantly,
this book can help you understand the C# language better.
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
V. Sarcar, Test Your Skills in C# Programming
https://doi.org/10.1007/978-1-4842-8655-5_1
Welcome to the first chapter of the book! Before you jump into it, let me
quickly remind you that in each of the chapters I’ll focus on some specific
topics that are important for you to learn in order to master programming in
C#. In addition, you probably know that C# heavily depends on .NET. So,
before we review your C# programming skills, I will review some .NET
basics. This chapter covers the following topics:
The important concepts in .NET
The basic programming constructs in C#
Use of some useful data types including the var type
Use of some useful operators and explicit casting
Use of the selection statements and case guards
Use of the iteration statements
Use of the jump statements
Use of the ternary operator
Let us start now.
Theoretical Concepts
This section includes questions and answers on C# programming basics and
defines common terms in Microsoft .NET.
Note I have covered the most common terms in the previous Q&As.
But there are many other terms, and you may like to know about them.
So, I recommend you visit https://docs.microsoft.com/en-
us/dotnet/standard/glossary#implementation-of-
net . Here you’ll also see that .NET is always fully capitalized, never
written as “.Net.”
Preliminary Concepts in C#
1.T10 Can you distinguish a project from a solution?
Answer:
A project contains executables and libraries (in general) to make an
application or module. Its extension is .csproj in C# (e.g., Console
Application, Class Library, Windows Form Application, etc.).
A solution is a placeholder that contains different logically related
projects. Its extension is .sln. (e.g., a solution can contain a console
application project and Windows form project application together).
1.T11 What is the significance of using System ?
Answer:
It allows you to access all classes defined in the System namespace
(but not the classes in its child namespaces).
1.T12 How do you choose among a float, a double, and a decimal
data type?
Answer:
If you want more precision in an application such as an application that
deals with financial computations or interest rates, use a decimal data
type . But when optimizing performance or minimizing the storage is more
important than precisions, you can use a double (or float) data type.
The online documentation at
https://docs.microsoft.com/en-
us/dotnet/csharp/language-reference/builtin-
types/floating-point-numeric-types tells the following:
Author’s Note: The C# types float, double, and decimal represent the
.NET types System.Single, System.Double, and
System.Decimal, respectively. All these floating-point numeric types
are used to represent real numbers, and their default value is 0.
1.T13 “If you want more precision in an application such as the
applications that deal with financial computations or interest rates, use
a decimal data type.” Can you explain this statement with a program?
Answer:
The float and double data types work best for numbers with base 2. But
the decimal data type is used for the numbers with base 10. So, as said
before, you should pick decimals when your calculations (such as financial
computations) are very sensitive to rounding errors. To understand this,
consider the following code:
var b = 6;
decimal flag1 =(decimal) 10.0 / b;
double flag2 = 10.0/ b;
float flag3 = (float)10.0 / b;
Console.WriteLine(flag1);
Console.WriteLine(flag2);
Console.WriteLine(flag3);
Now compile and run this code. You’ll see the following output:
1.6666666666666666666666666667
1.6666666666666667
1.6666666
Console.WriteLine("***Illustration: do-while
loop***");
int j = 1;
do
{
Console.WriteLine($"Inside loop: j is now
{j}");
j++;
} while (j < 1);
Console.WriteLine($"Outside loop: j's final value
is {j}");
Console.WriteLine("\n***Illustration: while
loop***");
j = 1;
while (j < 1)
{
Console.WriteLine($"Inside loop: j is now
{j}");
j++;
};
Console.WriteLine($"Outside loop: j's final value
is {j}");
When you run this code, you’ll get the following output:
POINT TO REMEMBER
Sometimes a do-while loop is referred to as a do loop. But I want you
to note that if you use do{..} without a while{..}, the C# compiler
raises the following error: CS1003 Syntax error, 'while'
expected.
You can see that I have created similar operations using a for loop and
a foreach loop . Now notice that if you want to print the values, like
1,3,5 (or any particular order you like), writing code using the for loop is
easier, and I need to change only the increment part of the for loop, as
follows (i.e., instead of i++, I’ll use i+=2):
Note Collections are advanced concepts. If you do not have any idea
about them, it is better to skip this question for now. You can learn about
collections later and come back here.
Programming Skills
Using the following questions you can test your programming skills in C#.
Note that if I do not mention the namespace, you assume that all parts of the
program belong to the same namespace.
Console.WriteLine("Hello");
class A
{
// Some code
}
class A
{
// Some code
}
Answer:
No. You’ll receive the following compile-time error:
Explanation:
In a namespace, you cannot declare multiple classes with the same
name. If you want to use the same name for your classes, you can place
them in separate namespaces. Here is a sample:
Console.WriteLine("Hello");
class A
{
// Some code
}
namespace AnotherNamespace
{
class A
{
// Some code
}
}
Answer:
This code can compile and run successfully. You’ll get the following
output:
Answer:
You’ll get the following output:
Explanation:
MaxValue and MinValue in this example help you to verify the
largest and smallest possible value of an int. So, the first two lines of this
output are straightforward. But it becomes interesting when you notice the
last line of the output.
Why did this happen? The answer is that, silently, the result of an
arithmetic operation on integral types can overflow, and you may be
unaware of it because you do not see any exception message. Here I
demonstrate such a case: I incremented an integer value by 1 when it was
already containing the maximum possible value. The result of this addition
operation causes you to get the minimum possible int value.
So, if you want to be careful, what should you do? You can use the
checked keyword like the following:
As a result, when you execute the previous code segment, you can see
the exception, as shown here:
Author’s Note: The unchecked operator can be used for the opposite
purpose.
1.P4 In the previous code ( 1.P3 ), I saw that by default there is no
exception message. So, I do not understand the need for an unchecked
operator. Can you please explain?
Answer:
The official documentation at
https://docs.microsoft.com/en-
us/dotnet/csharp/language-
reference/keywords/checked confirms that the overflow checking
can be enabled by compiler options, environment configuration, or the
checked keyword. So, when you use the compiler option in Visual
Studio’s Advanced Build Settings as shown in Figure 1-2, you enable
overflow checking by default for all the possible expressions in a similar
context.
Figure 1-2 Enabling overflow checking for the arithmetic expressions in a program
Now let us assume that you need to disable overflow checking for a
small number of expressions. So, instead of disabling the overflow checks
for all expressions, you can use the unchecked operator for these few
expressions.
1.P5 Can you compile the following code?
byte flag1 = 1;
byte flag2 = 2;
byte flag3 = flag1 + flag2;
Console.WriteLine($"The flag3 is: {flag3}");
Answer:
No. You’ll receive the following compile-time error:
Explanation:
See the official note at https://docs.microsoft.com/en-
us/dotnet/csharp/language-reference/builtin-
types/integral-numeric-types . You can see that byte is an
“Unsigned 8-bit integer,” whereas int is a “Signed 32-bit integer.” Small-
sized integers (8-bit and 16-bit integral types) do not have their arithmetic
operators. If required, C# implicitly can convert them to larger types as
required to support an operation. But you’ll see the compile-time error if
you try to assign the result back to the small integral type.
About the arithmetic operators in C#, the official documentation at
https://docs.microsoft.com/en-
us/dotnet/csharp/language-
reference/operators/arithmetic-operators says the
following:
So, what is the remedy for the code in 1.P5? You can apply an explicit
cast like the following:
5.25/0.0 = ∞
1.27/-0 = -∞
0/-2.5 = -0
0.0/0.0 = NaN
0/5 = 0
-0.0 = -0
-0.0f = -0
double.PositiveInfinity = ∞
-5*PositiveInfinity = -∞
float.PositiveInfinity = ∞
Explanation:
I have shown this example to remind you that you can see negative
infinity or negative zero in your program output. In C# programming, the
float and double types can have some special values: NaN (Not a Number),
+∞, -∞, and -0. There are certain operations that can produce these special
values. You have seen some of these operations. For example, you can see
that dividing a nonzero number by zero produces an infinite value. In this
case, the resultant sign is positive if both the numerator and the
denominator have the same sign only. You can also see that both the
constants, -0.0 and -0.0f, show the special value -0.
Now, Figure 1-3 shows a partial snapshot of the Double class from the
Visual Studio IDE so that you can see some of its constants for these special
values.
Figure 1-3 Special values inside the built-in Double class in C#
You can see that the Double class has the specific constants such as
NaN, PositiveInfinity, NegativeInfinity, and so on. It is
useful for you to note that the float and double types follow the
specification of the IEEE 754 format types. From the lower part of Figure
1-3, by seeing the naming conventions, you can get this hint.
1.P7 Can you compile the following code?
Console.WriteLine($"5/0 = {5 / 0}");
Answer:
No. You’ll receive the following compile-time error:
int b = 0;
Console.WriteLine($"5/0 = {5 / b}");
Answer:
This code can compile, but you’ll get the following runtime error:
Selection Statements
1.P9 Predict the output of the following code segment :
Answer:
You have seen a simple use of an if-else chain. In this case, you’ll
get the following output:
CheckNumber(25.5);
CheckNumber(125.5);
CheckNumber(10.000);
Once you run this code, you’ll get the following output:
Explanation:
This program demonstrates the following important points:
You should not assume that the default case should be placed at the end
of a switch block. However, it is a standard practice because this case is
evaluated at the end (if required). The official documentation (
https://docs.microsoft.com/en-
us/dotnet/csharp/language-
reference/statements/selection-statements ) also says
this:
The default case can appear in any place within a switch statement.
Regardless of its position, the default case is always evaluated last
and only if all other case patterns aren't matched.
Why do you see the warning? For the number 10.000, both the default
case and case >= 10.0 satisfy the matching criteria. But as per the
language documentation, the default case is evaluated only if other case
patterns are not matched. So, you see the output statement corresponding
to the case pattern case >= 10.0.
So, if you remove the equal sign (=) from this case and use case >
10.0 instead of case >= 10.0, you won’t see this warning, and the last
line of the output will be changed. It is because this time the supplied
number meets the criteria of the default case only. Here is the corresponding
output:
10 is equal to 10.0
1.P11 Can you compile the following program?
CheckNumbers(5,23);
CheckNumbers(12,5+7);
CheckNumbers(-7,239);
Explanation:
Here you see an example of a case guard (which is a Boolean
expression) inside a switch block . It is useful when a case pattern is not
expressive enough to specify the condition. Basically, this is an additional
condition that must be satisfied together with a matched pattern. You
specify a case guard after the when keyword, as shown in this program.
1.P12 Can you compile the following code?
EvaluateNumber(5);
EvaluateNumber(125);
EvaluateNumber(-2);
Explanation:
At the time of this writing, the C# compiler will not allow the control to
fall through from one switch section to the next within a switch
statement. Typically you use the break statement at the end of each switch
section. Sometimes you also see the use of the return, goto, and throw
statements to pass control out of a switch statement .
1.P13 Can you compile the following code?
ShowNumber(5);
ShowNumber(125);
ShowNumber(-2);
case < 5:
case < 50:
case < 100:
Console.WriteLine($"{num} is less than
100.");
break;
default:
Console.WriteLine($"{num} is greater
than or equal
to 100.");
break;
}
}
Answer:
Yes, this code can compile and run successfully. You’ll get the
following output:
Explanation:
You should not confuse it with the previous program. The C# compiler
allows you to specify multiple case patterns for one section of a switch
statement. So, this program can compile and run. But if you use something
like the following (the changes are in bold):
case < 5:
case < 50: Console.WriteLine("Some
statements.");
case < 100:
Console.WriteLine($"{num} is less than
100.");
break;
default:
Console.WriteLine($"{num} is greater
than or equal
to 100.");
break;
}
}
you’ll see the compile-time error:
Ternary Operator
1.P14 Predict the output of the following code segment :
int flag = 7;
string output= flag<3?"less than 3":"greater than
3";
Console.WriteLine($"{flag} is {output}");
Answer:
You have seen a simple use of a conditional operator (?:). It is also
known as the ternary operator because it takes three operands. It evaluates a
Boolean expression and returns the result of one of the two expressions,
depending on whether this expression evaluates to true or false. When you
use it, you use the following form:
expression? true_case: false_case,
Here if the condition of the expression is true, true_case is
evaluated; otherwise, the false_case is evaluated.
Since the Boolean expression becomes false in this program, you’ll
get the following output:
7 is greater than 3
Author’s Note: Conditional expressions have become more useful over
a period of time. For example, in C# 7.2 onward, conditional ref
expressions are more flexible: you can assign a ref local or ref
readonly local variable conditionally with such an expression. In C# 9.0
onward, conditional expressions are also target-typed. This is the beginning
of the book, and we have not covered the advanced concepts yet. So, I
prefer to skip those discussions here. If interested, you can take a quick look
at these new capabilities of the conditional expression at
https://docs.microsoft.com/en-
us/dotnet/csharp/language-
reference/operators/conditional-operator .
Iteration Statements
1.P15 Here is a code segment that uses a while loop :
int i = 0;
while (i < 3)
{
Console.WriteLine($"The current value: {i}");
i++;
}
Answer:
Yes, but you’ll fall into an infinite loop:
1.P17 Can you compile the following code?
Answer:
Yes, but you’ll fall into an infinite loop again.
1.P18 Predict the output of the following code segment:
Answer:
This code can compile and run successfully. You’ll get the following
output:
Explanation:
Each time the loop is executed, the value of k is incremented by 3. The
upper limit was set to 8. So, you see these values: 0, 0+3=3, 3+3=6.
1.P19 Can you compile the following code?
int i = 5;
while (i)
{
Console.WriteLine($"i is now {i}");
i++;
}
Answer:
No. In C# programming, this type of coding is not allowed. You’ll
receive the following compile-time error:
int i = 5;
while ()
{
Console.WriteLine($"i is now {i}");
i++;
}
Answer:
No. You’ll receive the following compile-time error:
int j = 0, m = 0;
while (true)
{
Console.WriteLine($"j is now {j}");
m++;
}
Answer:
Yes, but you’ll fall into an infinite loop again.
Explanation:
The Boolean expression for the while statement is always true, so
the loop cannot terminate.
1.P22 Can you compile the following code?
int flag = 1;
while (flag != 3)
{
Console.WriteLine($"flag is now {flag}");
flag++;
if (flag == 2)
goto level_1;
}
flag = 50;
Console.WriteLine($"flag is now {flag}");
level_1:
flag = 100;
Console.WriteLine($"flag is now {flag}");
Answer:
This code can compile and run successfully. You’ll get the following
output:
flag is now 1
flag is now 100
Explanation:
When the variable flag becomes 2, it jumps to level_1. There it
sets a new value (100) for the flag variable and finally prints this
information. So, you have seen an example of a goto statement here.
1.P23 Here is a code segment that uses a while loop:
int i = 0;
while (i < 3)
{
Console.WriteLine($"The current value: {i}");
i++;
}
Can you write an equivalent program using a goto statement?
Answer:
Here is a sample:
int k = 0;
level_2:
Console.WriteLine($"The current value: {k}");
k++;
if (k < 3) goto level_2;
Jump Statements
1.P24 Can you compile the following code?
DummyMethod(true);
void DummyMethod(bool b)
{
b = false;
goto level_3;
}
level_3:
Console.WriteLine("Reached to the level_3");
Answer:
No. You’ll receive the following compile-time error:
Explanation:
You’ll see the compile-time error when the level referenced by the
goto statement are not placed within the scope of the goto statement.
POINTS TO NOTE
To get out of a nested loop or inside a switch statement to transfer
control, you can use goto statements. But in general, I try to avoid
these statements in my program. Microsoft also recommends the same
for both C++ and C# programmers. For C++ programmers, Microsoft
says the following (see https://docs.microsoft.com/en-
us/cpp/cpp/goto-statement-cpp?view=msvc-170 ):
Console.WriteLine("***Illustration: break vs
continue ***");
int i = 1;
while (i != 5)
{
Console.WriteLine($"Now, i= {i}");
i++;
if (i == 4)
{
Console.WriteLine(" Entered inside if
loop");
break;
//continue;
}
}
When you run this code, you’ll see the following output:
Now replace the break statement with the continue statement and
run the program again. This time you’ll see the following output (notice the
change in bold):
var i = 25;
var j1 = 25.5f;
var j2 = 25.5;
var j3 = 25.5M;
var k = 'c';
var l = "hello";
Console.WriteLine($"The type of i is
{i.GetType()}");
Console.WriteLine($"The type of j1 is
{j1.GetType()}");
Console.WriteLine($"The type of j2 is
{j2.GetType()}");
Console.WriteLine($"The type of j3 is
{j3.GetType()}");
Console.WriteLine($"The type of k is
{k.GetType()}");
Console.WriteLine($"The type of l is
{l.GetType()}");
Answer:
Yes, this code can compile and run successfully. You’ll get the
following output:
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
V. Sarcar, Test Your Skills in C# Programming
https://doi.org/10.1007/978-1-4842-8655-5_2
In C# programming, you have various data types. For example, to store integers with
various ranges, you can use the integral types (int, short, long, byte, sbyte, uint, ushort,
ulong, and char). Similarly, you have floating-point types (float, double, and decimal) to
store floating-point numbers. These types support different levels of precision and range.
You also have bool type to store true or false values, as well as the string data type to store
text. There are also custom data types, which you’ll learn about in Part II of this book.
The goal of this chapter is not to check whether you remember all of these types and
the ranges they support. I know that they cannot be memorized in one reading, and the
information about their supporting ranges is easily available from a reference book or
online material. In the first chapter, I covered some useful data types. In this chapter, I’ll
cover two more important data types: strings and arrays. You can guess the reason: they
also belong to the most common building blocks in C# programming. Once you finish this
chapter, you will be able to answer the following questions:
How can you use the string data type in your program?
How can you use the common built-in methods from the String class?
How is a String variable different from a StringBuilder?
How can you convert a string to an int?
How can you use a nullable reference type in a program?
How do you create arrays in C#?
What are the different types of C# arrays, and how can you use them?
How can you use the common built-in methods from the System.Array class?
How can you iterate over a string or an array?
Let us review and test your understanding of these topics now.
Theoretical Concepts
This section includes the theoretical questions and answers about strings and arrays in C#.
Strings in C#
2.T1 What is a string data type in C#?
Answer:
You use the string type to store the text of an arbitrary length . In C#, you can use the
string keyword to represent strings. To create a string data type or assign a value to it,
you use double quotes as follows (this code initializes the string variable with a regular
string literal):
You need to remember that these are an immutable sequence of Unicode characters.
Author’s Note: A string object is a sequential collection of System.Char objects. A
System.Char object corresponds to a UTF-16 code unit. So, you can say that a string
represents text that is a sequence of UTF-16 code units. In C#, the string keyword is an
alias for System.String. Similarly, you can use the int and char keywords for the
.NET types System.Int32 and System.Char, respectively.
2.T2 We can print a string variable in the console using different methods. Can
you demonstrate some of them?
Answer:
Here is some sample code with supporting comments for your reference :
// Using String.Format
string str = String.Format("Hi {0}, you have ${1} left.",
name, balance);
Console.WriteLine(str);
When you run this code, you’ll see that each line produces the same output as follows:
When you run this code, you’ll see the following output:
You can see the invocation of the String.Join method makes a more readable
output in this case.
Author’s Note: The Q&A in 2.T6 belongs to the advanced topics of C#. You may
need to understand the concept of objects, references, and string interning. The need for
string interning can be understood through the Flyweight design pattern. But I have made
it easier for you to understand this concept with a small code segment. Still, if it is tough
for you, you can skip it for now!
2.T6 Suppose you see the following code segment:
When you run this code, you’ll see the following output:
True
But it is interesting to note that if you have the following code segment (see the
changes in bold):
True
False
Confused about the last line of output? Let me explain: though str3 is a constant,
str4 is not. This is because the combination of a constant and a variable cannot be a
constant. So, the compiler does not know the value of this expression in advance.
Therefore, in this case, str1 and str2 do not point to the same object; instead, they
point to different objects .
numbers[0] = 5;
numbers[1] = 12;
numbers[2] = 7;
You can display these elements using a for loop (though it is optional) as follows:
This is a simple demonstration, and we know that there are only three elements in this
array. But it is always better to use the Length property of the array to traverse the array.
Here is an example:
// Alternative approach-2.
int[] numbers = new int[] { 5, 12,7 };
// Alternative approach-3.
int[] numbers = { 5, 12, 7 };
// Alternative approach-4.
// For an explicit instantiation, var is also allowed
var numbers = new int[] { 5, 12, 7 };
Here you used integer arrays. If needed, you can create other types of arrays too.
2.T9 What is a multidimensional array in C#?
Answer:
Multidimensional arrays come in two flavors: rectangular and jagged.
In a rectangular array, each row has an equal number of columns.
A jagged array is an array of arrays, possibly of different sizes. In other words, in a
jagged array, each row can have a different number of columns. A jagged array is also
known as a ragged array.
POINTS TO REMEMBER
Remember that to declare a rectangular array, you use commas to separate each
dimension such as int[,] elements = new int[row, column]; .
For a jagged array, you use successive square brackets to represent each
dimension such as int[][] matrix = new int[outmost_dimension]
[]; .
int[,] rectArray = {
{10, 20,30},
{40, 50,60},
};
Alternatively, you can see the following style:
2.T12 How can you print the jagged array elements in 2.T11 using a foreach
loop?
Answer:
Here is some sample code that uses the foreach loop to print the elements in that
array :
Programming Skills
Using the following questions, you can test your programming skills in C#. Note that if I
do not mention the namespace, you assume that all parts of the program belong to the
same namespace.
String Fundamentals
2.P1 Predict the output of the following code segment :
Answer:
You’ll get the following output:
Explanation:
This code segment shows that you can follow different approaches to concatenate two
strings.
2.P2 Predict the output of the following code segment:
Answer:
You’ll get the following output:
Explanation:
This program shows the following points:
You can use the built-in Length method to calculate the number of characters present
in a string.
This program shows you a way to read the individual characters of a string. You may
notice that a valid index value starts from 0 and must be less than the length of the
string.
This program also shows how can you print single quotes in the console.
Additional Note:
In Chapter 1, I told you that strings are enumerable. So, you can use a foreach loop
to print the individual characters of a string. Here is a sample:
Answer:
You’ll get the following output:
Explanation:
This program shows you that the following:
You can print double quotes without escape sequence too.
You can convert a string to uppercase or lowercase using the built-in ToUpper() and
ToLower() methods, respectively.
2.P4 How can you print a URL such as https://google.com ?
Answer:
Here is a sample code:
POINTS TO REMEMBER
Here you have seen the use of the verbatim identifier (@). You can use this special
character for various purposes. One such usage is that you can use this character to
enable C# keywords to be used as identifiers. The following code with comments
describes this:
// string for=“abc”; // compile-time error
string @for= “abc”;// ok
Answer:
You’ll get the following output:
Explanation:
The Trim() method can be used to remove all leading and trailing whitespace
characters from a string.
2.P6 Predict the output of the following code segment:
Answer:
You’ll get the following output:
Explanation:
This program shows how to use the Trim() method to remove all leading and
trailing occurrences of a set of characters (whitespace, t, dot(.), and *) from a string.
The TrimEnd() method can do this, but it operates from the end of the current string.
2.P7 Predict the output of the following code segment:
Additional Note:
You can test whether a string is null or empty using the IsNullOrEmpty(...)
method . Here is a sample:
Console.WriteLine(string.IsNullOrEmpty(emptyString));//True
Console.WriteLine(string.IsNullOrEmpty(nullString));//True
Note I have shown you the use of the nullable strings (see string?). Nullable
reference types have been allowed since C# 8.0. If you use string instead of
string?, you’ll see the following warning message: CS8600 Converting
null literal or possible null value to non-nullable type.
Answer:
You’ll get the following output:
Original line:'Reviewing the concepts of C#.'
Updated line:'**Reviewing the concepts of C#.###'
Explanation:
Padding in the string is used to add a space or other character at the beginning or end
of a string. This code segment shows you how to pad a specific character at the beginning
(or at the end) of a string using the built-in String class methods: PadLeft() and
PadRight(). You may note that these methods have overloaded versions too.
Note Remember that in C#, a String object is immutable. So, once created, its
value cannot be modified again. Methods that appear to modify a String object
actually return a new String object that contains these modifications.
2.P9 A string is a palindrome string if the reverse of it is the same as the original
string. Can you write a program to check whether a string is a palindrome?
Answer:
You can reverse the string and then check whether the original string and the reversed
strings are the same. Here is a simple program for this:
// Console.WriteLine("\n---2.P9---");
Console.WriteLine("Enter the string:");
string? inputStr = Console.ReadLine();
string? reverseStr = ReverseString(inputStr);
Validate(inputStr, reverseStr);
// Reversing a string
static string ReverseString(string str)
{
char[] tempArray = str.ToCharArray();
// Reverses the sequence of the elements
Array.Reverse(tempArray);
// Change the reversed array to a string
string reverseStr = new(tempArray);
// Return this string
return reverseStr;
}
}
Output:
Here is a sample output (positive case):
Author’s Note: I know what you are thinking. Yes, you can beautify this program
using the exception handling mechanism that you’ll learn/test in Chapter 8. For now, let us
focus on the core part only. I also want you to note that the Equals method can
determine whether two instances of System.String objects have the same value.
2.P10 Can you write a program to demonstrate the distinction between a String
variable and a StringBuilder variable?
Answer:
Let’s consider the following program and output. You will see that no new instance is
created for StringBuilder .
Console.WriteLine("***String vs StringBuilder.***");
ObjectIDGenerator idGenerator = new();
bool firstTime = false;
***String vs StringBuilder.***
Answer:
This code can compile and run successfully. You’ll get the following output:
125
17
Explanation:
The online Microsoft documentation (see
https://docs.microsoft.com/en-us/dotnet/csharp/programming-
guide/types/how-to-convert-a-string-to-a-number ) says the
following:
You use Parse or TryParse methods on the numeric type you expect the string
contains, such as the System.Int32 type. The Convert.ToInt32 method uses Parse
internally.
In the first case of this code segment, the + operator performs string concatenation. So,
12+5 becomes 125 (i.e., "12"+"5"="125"). In this case, ToString is called on the
nonstring value. In the second case, you converted the string to an int using the Parse
method. So, this time, you see the result of addition (12+5=17).
This online link also suggests the following important point:
When calling a Parse method, you should always use exception handling to catch
a FormatException when the parse operation fails.
Since I have not discussed exception handling yet, I have ignored this suggestion in
this code segment (2.P11).
2.P12 Predict the output of the following code segment:
Answer:
The TryParse method call was successful. So, you’ll get the following output:
Additional Note:
TryParse and Parse are often used in C# programming. To understand the
difference between these two methods, I suggest you read the Microsoft documentation (at
the link mentioned in 2.P11). It says the following:
The Parse method returns the converted number; the TryParse method returns a
boolean value that indicates whether the conversion succeeded, and returns the
converted number in an out parameter. If the string isn't in a valid format, Parse
throws an exception, but TryParse returns false.
2.P13 There are four occurrences of the letter “e” in the following string variable:
Once you run this code, you’ll see the following output:
Author’s Note: Alternatively, you can use a for loop to find the concurrences of ‘e’
and maintain a counter to track how many ‘e’ you have found. If the counter is 3, you can
return the index value. I leave this exercise for you. You may note that if the particular
character is not found, the IndexOf method returns -1.
Array Fundamentals
2.P14 Predict the output of the following code segment :
Explanation:
You have seen an example of a rectangular array with the dimensions 2x3. Notice that
I have not supplied the value for elements[1,1], so it is initialized with the default
value of int (because it is an int array). In this example, each row has an equal number
of columns (three), so it is a rectangular two-dimensional (2D) array .
Note The default values of numeric array elements are zero, but for the reference
type elements, these are set to null. So, if you use an array that can hold string elements
but you do not supply the required values, you’ll see the nulls are set as defaults,
instead of zeros.
Answer:
You’ll get the following output:
Element at [0][1]=2
Element at [1][3]=8
Element at [2][1]=12
The last element's index of jaggedArray[1] is 5
The first element's index of jaggedArray[2] is 0
Explanation:
The first three lines of output are straightforward. The GetUpperBound and
GetLowerBound method s are used to retrieve the last and first element’s index of the
specified dimension in an array.
2.P16 Suppose the jagged array is the same as in P2.15. Can you predict the
output of the following line?
Console.WriteLine($"The jaggedArray[2].GetUpperBound(1) is
{jaggedArray[2].GetUpperBound(1)}");
Answer:
You’ll receive the following runtime error:
Answer:
You’ll get the following output:
The rectArray.Length= 12
Explanation:
The Length property gives the total number of elements in all dimensions of the
array. Here the number of elements is 3*4=12.
2.P18 Predict the output of the following code segment:
Answer:
You’ll get the following output:
The jaggedArray3.Length= 3
The jaggedArray3[1].Length= 5
Answer:
You’ll get the following output:
Explanation:
The Sort() method sorts the elements of an array in ascending order. The
reverse() method reverses these elements.
2.P20 Predict the output of the following code segment:
Answer:
The value at index location 4 is changed to 50, but other elements of the array were
unchanged. So, you’ll get the following output:
scores[3]=4
scores[4]=50
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
V. Sarcar, Test Your Skills in C# Programming
https://doi.org/10.1007/978-1-4842-8655-5_3
In the previous two chapters, you saw how to use the built-in simple types as well as
other types such as strings and arrays. Now the question is: can we categorize these
types? The answer is yes. Broadly, you can divide all C# types into value types and
reference types. You can further divide the value types into the following categories:
Structure types
Enumeration types
All the simple types are value types in C#. But the strings and arrays are
reference types. Part II of this book focuses on object-oriented programming, and
classes and objects are the foundation for that. A class is also a reference type.
Before you get to that part of this book, in this chapter we will do a quick review of
the value types.
POINTS TO NOTE
Note the following points before you read further:
We often refer to the bool type, char type, integral, and floating-point numeric
types as simple types. All the simple types are built-in value types in C#.
In the unified type system of C#, all predefined and user-defined types inherit
directly or indirectly from System.Object. This applies to any of the value
types or reference types. The object type is an alias for System.Object.
Microsoft says that although string is a reference type, the equality operators
== and != are defined to compare the values of string objects, not references.
Value-based equality makes testing for string equality more intuitive. You can
refer to the online documentation at
https://docs.microsoft.com/en-
us/dotnet/csharp/language-reference/builtin-
types/reference-types for more information.
Theoretical Concepts
This section includes the theoretical questions and answers on inheritance.
Enumerations in C#
3.T1 What is an enumeration type in C#? How do you define one?
Answer:
It is a value type . The enum keyword is used for the enumeration type in C#. It
allows you to specify a set of named numeric constants. To define an enumeration
type in C#, you use the enum keyword. Here is some sample code to define an
enumeration type called ErrorTypes that has three members, NetworkError,
CodeError, and DeviceError:
enum ErrorTypes
{
NetworkError,
CodeError,
DeviceError,
};
//
// Summary:
// Provides the base class for enumerations.
This definition shows that Enum is an abstract class that inherits from
ValueType. On further investigation, you’ll see that ValueType is an abstract
class that is defined in the System namespace.
3.T3 How are the enum members organized?
Answer:
By default, the associated constant values of enum members are of type int;
they start with zero and increase by one following the definition text order. But you
can specify any other integral numeric type as an underlying type. In addition, you
can explicitly assign specific values to an enumeration if you want. Here is a
sample:
enum Values
{
Val1 = 25,
Val2 = 52,
Val3 = 65,
Val4
};
The unassigned enum members keep incrementing from the last explicit value.
So, the previous code segment is equivalent to the following:
enum Values
{
Val1 = 25,
Val2 = 52,
Val3 = 65,
Val4 = 66
};
Note The word enumeration originates from the word enumerate. The
dictionary definition of enumerate is “to count off, one after the other,” which is
the intent of enumerations.
In this case, you can explicitly cast one enum type to another as follows:
Measurement position =
(Measurement)Alignment.TowardsLeft;
Structures in C#
3.T6 What is a struct type in C#? How do you define one?
Answer:
A struct type is a value type that can encapsulate data and related functionality.
You use the struct keyword to define a structure in C#. Here is a sample:
struct Employee
{
public int Id;
public string Name;
public Employee(int id, string name)
{
this.Id = id;
this.Name = name;
}
}
POINTS TO REMEMBER
The structures in C# follow the value semantics. This means that in the case of a
struct variable, an instance of the type is copied. See
https://docs.microsoft.com/en-
us/dotnet/csharp/language-reference/builtin-
types/struct . Here you’ll find that Microsoft recommends you use
immutable struct types in general. So, the question is how to make a structure
immutable. One possible option is to make proper use of readonly modifiers.
Starting with C# 7.2, this can be done.
But there are cases when you cannot declare the whole structure as
readonly. What to do then? You can use the readonly modifier to mark the
instance members that should not modify the state of the struct.
Employee emp3;
emp3.Name = "Kate";
emp3.Id = 3;
Console.WriteLine($"Name: {emp3.Name}, ID:{emp3.Id}");
struct Employee
{
public int Id;
public string Name;
public Employee(int id, string name)
{
this.Id = id;
this.Name = name;
}
}
Once you compile and run this program, you’ll see the following output:
3.T10 Can you highlight some differences between structures and class in
C#? When should I prefer structures over classes?
Answer:
Structures are value types, but classes are reference types. A structure type can’t
inherit from another class or structure type, and it can’t be the base of a class. Also,
you cannot have a default constructor (explicit parameterless constructor) for a
structure before C# 10.0.
For lightweight objects, you should prefer structures. Microsoft’s online
documentation ( https://docs.microsoft.com/en-
us/dotnet/csharp/language-reference/builtin-types/struct
) suggests the following:
Typically, you use structure types to design small data-centric types that
provide little or no behavior. For example, .NET uses structure types to
represent a number (both integer and real), a Boolean value, a Unicode
character, a time instance. If you're focused on the behavior of a type,
consider defining a class. Class types have reference semantics. That is, a
variable of a class type contains a reference to an instance of the type, not
the instance itself.
3.T11 Fill in the blank: In C#, structures are inherited
from______________.
Answer:
System.ValueType . To verify this, you can look at the IL code of a
structure. For example, in 3.T9, you saw the following structure:
struct Employee
{
public int Id;
public string Name;
public Employee(int id, string name)
{
this.Id = id;
this.Name = name;
}
}
Let me break down this IL code. Figure 3-1 shows a partial snapshot (follow the
arrow).
Figure 3-1 IL code for the Employee structure
It is also important to remember what Microsoft says about this (see
https://docs.microsoft.com/en-
us/dotnet/api/system.valuetype?view=net-6.0 ):
Although ValueType is the implicit base class for value types, you cannot
create a class that inherits from ValueType directly.
Note The structures in C# are not great promoters of inheritance. You will learn
that a structure type can’t inherit from another class or structure type and it can’t
be the base of a class. However, a structure type can implement interfaces. But
from Figure 3-1, you can see that they implicitly derive from
System.ValueType. I want you to note this fact. I discuss inheritance in
detail in Chapter 5.
Programming Skills
Using the following questions, you can test your programming skills in C#. Note
that if I do not mention the namespace, you can assume that all parts of the program
belong to the same namespace.
Console.WriteLine($"Type1= {(int)Codes.Type1}");
Console.WriteLine($"Type2= {Codes.Type2}");
Console.WriteLine($"Type3= {(int)Codes.Type3}");
enum Codes
{
Type1,
Type2,
Type3,
};
Answer:
This code can compile and run successfully. You’ll get the following output:
Type1= 0
Type2= Type2
Type3= 2
Explanation:
Microsoft ( https://docs.microsoft.com/en-
us/dotnet/csharp/language-reference/builtin-types/enum )
says the following:
By default, the associated constant values of enum members are of type int;
they start with zero and increase by one following the definition text order.
You can explicitly specify any other integral numeric type as an underlying
type of an enumeration type. You can also explicitly specify the associated
constant values.
For any enumeration type, there exist explicit conversions between the
enumeration type and its underlying integral type. If you cast an enum value
to its underlying type, the result is the associated integral value of an enum
member.
I did not apply casting to the second member before I printed it to the console;
so it has appeared as shown.
3.P2 Predict the output of the following code segment:
Console.WriteLine($"Val4={(int)Values.Val4}");
enum Values
{
Val1 = 25,
Val2 = 52,
Val3 = 65,
Val4
};
Answer:
You’ll get the following output:
Val4=66
Explanation:
The previous constant value was 65, so it is increased by 1 for the next member.
3.P3 Predict the output of the following code segment:
Explanation:
See the explanation of 3.P1. You can also see why the integral value of
TrafficLight.Green is 1 and TrafficLight.Yellow is 2. So, for the
constants 0, 1, and 2, you have corresponding enum members. But for other
constants, you do not have any such members.
3.P4 Can you compile the following code?
Console.WriteLine($"Val2= {(byte)Values.Val2}");
enum Values : byte
{
Val1,
Val2,
Val3
};
Answer:
This code can compile and run successfully. You’ll get the following output:
Val2= 1
Explanation:
The code explanation of 3.P1 shows you that Microsoft allows you to use any
integral type such as byte here.
3.P5 Can you compile the following code?
Answer:
No. You’ll receive the following compile-time error:
Explanation:
System.Int16 is an alias for the short type. This example shows you how
you can retrieve the underlying type of an enumeration.
3.P7 Predict the output of the following code segment:
enum Numbers
{
Num1 = 75,
Num2 = -17,
Num3,
Num4=0,
Num5=95
};
Answer:
You’ll get the following output:
Explanation:
The GetValues method in the abstract class Enum returns an array that
contains the values of the constants in the enum type. But have you noticed the
order? The Microsoft documentation at
https://docs.microsoft.com/en-
us/dotnet/api/system.enum.getvalues?view=net-6.0 describes
this:
The elements of the array are sorted by the binary values of the enumeration
constants (that is, by their unsigned magnitude).
For example, consider the values -1, 0, and 1. As per this documentation, these
values will be sorted as 0, 1, and -1. In addition, probably you have noticed the
value of Num3. You can see that it is increased by 1 from its previous value.
3.P8 Predict the output of the following code segment:
Answer:
This code can compile and run successfully. You’ll get the following output:
Num2 is storing 0
Num3 is storing 1
Num4 is storing 100
Num1 is storing -12
Explanation:
The output is straightforward. The order of this kind of output is described in the
previous explanation (see 3.P7).
3.P9 Predict the output of the following code segment:
Explanation:
The reason for this circular definition is as follows: Num2 is getting values from
TrafficLight.Green. So, you understand that Num3 is dependent on that
value (since it will be incremented by 1). But again, TrafficLight.Green is
dependent on Num3.
3.P10 Predict the output of the following code segment:
class Test
{
int flag = 2;
enum Codes
{
Code1 = 1,
Code2 = flag,
Code3 = 3
};
}
Answer:
No. You’ll receive the following compile-time error:
CS0120 An object reference is required for the non-
static field, method, or property 'Test.flag'
Additional Note:
The enum members must be constant. If you make the flag variable static
readonly and use the following code:
class Test
{
static readonly int flag = 2;
enum Codes
{
Code1 = 1,
Code2 = flag, // Error :CS0133
Code3 = 3
};
}
class Test
{
const int flag = 2;
enum Codes
{
Code1 = 1,
Code2 = flag, // OK now
Code3 = 3
};
}
Console.WriteLine($"Type0=
{(int)Sample.ErrorTypes.NetworkError}");
Console.WriteLine($"Type2= {(Sample.ErrorTypes)2}");
class Sample
{
public enum ErrorTypes
{
NetworkError,
CodeError,
DeviceError,
}
}
Answer:
Yes, this code can compile and run successfully. You’ll get the following output:
Type0= 0
Type2= DeviceError
Additional Note:
This code shows that you can define an enum type inside a class.
3.P12 Can you compile the following code?
class Sample
{
public enum ErrorTypes
{
NetworkError,
CodeError,
DeviceError,
}
Answer:
No. You’ll receive the following compile-time error:
Additional Note:
Instead of the nested enum, if you use a nested class, you’ll get a similar
compile-time error.
Console.WriteLine(ErrorTypes.NetworkError &
ErrorTypes.CodeError);
Console.WriteLine(ErrorTypes.DeviceError |
ErrorTypes.CodeError |
ErrorTypes.NetworkError);
[Flags]
public enum ErrorTypes
{
NoError = 0,
NetworkError = 1,
CodeError = 2,
DeviceError = 4,
Answer:
You’ll get the following output:
NoError
Explanation:
You have seen an example of a flag enum usage (notice the attribute [Flags]
before the class definition). It is useful to combine enum members. Microsoft says
the following (see https://docs.microsoft.com/en-
us/dotnet/csharp/language-reference/builtin-types/enum ):
interface IPerson
{
string Occupation();
}
Answer:
Yes, this code can compile and run successfully. You’ll get the following output:
Additional Note:
You could use the simplified new statement (Employee emp1 = new ();)
to get the same output.
3.P15 Can you compile the following code?
struct Person
{
string Occupation()
{
return String.Empty;
}
}
Answer:
No. You’ll receive the following compile-time error:
Explanation:
The Microsoft documentation (see https://docs.microsoft.com/en-
us/dotnet/csharp/language-reference/builtin-types/struct
) says the following:
A structure type can’t inherit from other class or structure type and it can't
be the base of a class. However, a structure type can implement interfaces.
This is the reason you’ll see the same error for the following code:
class Person
{
// Some code
}
struct Codes
{
int _error1 = 1;
}
Answer:
This code is interesting. Before C# 10.0, you’ll receive a compile-time error for
this code. For example, if you use this code in C# 9.0, you’ll see the following error:
When I documented this Q&A, this code was working fine in C# 10. But once I
updated Visual Studio to version 17.2.3, I was seeing the following compile-time
error:
To address the issue, in .NET SDK 6.0.200 (VS 17.1) the compiler no longer
synthesizes a parameterless constructor. If a struct contains field initializers
and no explicit constructors, the compiler generates an error. If a struct has
field initializers it must declare a constructor, because otherwise the field
initializers are never executed.
3.P17 Can you compile the following code?
struct Codes
{
public Codes()
{
// Some code
}
}
Answer:
Starting with C# 10, you won’t see any compile-time error. But before C# 10,
you’ll receive a compile-time error. For example, if you use this code in C# 9.0,
you’ll see the following error:
struct Codes
{
Codes()
{
// Some code
}
}
Answer:
No. You’ll receive the following compile-time error:
CS8958 The parameterless struct constructor must be
'public'.
Explanation:
See the online documentation at https://docs.microsoft.com/en-
us/dotnet/csharp/language-reference/proposals/csharp-
10.0/parameterless-struct-constructors . It confirms this behavior
by saying the following:
For an immediate reference, you can refer to the following code with the
supporting comments:
struct S1 { } // OK
struct S2 { public S2() { } } // OK
struct S3 { internal S3() { } } // Error CS8958
struct Codes3
{
protected int _error;
}
Answer:
No. You’ll receive the following compile-time error:
Explanation:
The Microsoft documentation (see https://docs.microsoft.com/en-
us/dotnet/csharp/misc/cs0666?
f1url=%3FappId%3Droslyn%26k%3Dk (CS0666) confirms this behavior by
saying the following:
Additional Note:
For the same reason, you cannot use virtual members in a structure. The
following code:
struct Codes3
{
public virtual void SomeMethod() { }
}
Now, it is easy to guess that if you try to use an abstract method inside a
structure, you’ll also see the compile-time error. For example, the following code:
struct Codes3
{
public abstract void SomeMethod(); // Error CS8958
}
struct Employee
{
public string Name;
public int Id;
public Employee(string name, int id)
{
Name = name;
}
public override string ToString()
{
string emp = Name + " has ID " + Id;
return emp;
}
}
Answer:
You’ll see the following output:
Explanation:
This is expected behavior now. Microsoft (
https://docs.microsoft.com/en-us/dotnet/csharp/language-
reference/builtin-types/struct ) confirms this by saying the
following:
A constructor of a structure type must initialize all instance fields of the typ
e.
public Point()
{
_yCoordinate = 2;
}
public override string ToString()
{
string temp= "(" + _xCoordinate + "," +
_yCoordinate +
")";
return temp;
}
}
Answer:
You’ll receive the following compile-time error:
Point_1: (1,2)
Point_2: (0,0)
Explanation:
In 3.P17, you saw that C# 10 allows you to place a public parameterless
constructor. So, this also does not cause any compile-time error. In 3.P16, you saw
that C# 10 allows you to perform field initialization inside a structure, but in that
case, you must have an explicitly defined constructor. This condition is also met in
this example. So, when you initialized the x coordinate, there is no compile-time
error.
Now all instance fields of the structure are assigned. So, this code is free from
compile-time errors.
So, the first line of output is expected. There is no magic. But the second line of
the output makes the program interesting. It is because you see the default values of
an int as the coordinate value. How is this possible? To understand this, you have to
read the online documentation ( https://docs.microsoft.com/en-
us/dotnet/csharp/language-reference/builtin-types/struct
) that says the following:
Additional Note:
You can use the simplified expressions here. For example ,
You can replace the line Point point1 = new Point(); with Point
point1 = new ();
In the same way, you can use the simplified default expression as Point
point2 = default;
Lastly, the ToString method also can be written as follows:
Beginning with C# 11 if you don’t initialize all fields in a struct, the compiler
adds code to the constructor that initializes those fields to the default value.
But, in 3P.16, you came to know about the breaking changes, which is an
important point. You’ll see the discussions on autodefault structs in Chapter 15.
struct Employee
{
public string Name;
public int Id;
public Employee( string name, int id)
{
Name = name;
Id = id;
}
public override string ToString()
{
string emp= Name+ " has ID " +Id;
return emp;
}
}
Answer:
Yes, this code can compile and run successfully. You’ll get the following output:
Sam has ID 1
Sam has ID 2
Kate has ID 2
Explanation:
You saw an example of using the with expression. It helps you to produce a
copy of a structure-type instance with the specified properties and fields modified.
You can use object initializer syntax to modify a member with the new values. This
whole process is also known as nondestructive mutation. You use this technique
when you need to copy an instance with some modifications.
Additional Note:
The with expression can produce a copy of its operand with the specified
properties and fields modified. This feature has been available since C# 9.0. But you
can apply this feature to a structure instance since C# 10 .
OceanofPDF.com
Part II
Object-Oriented Programming
Object-Oriented Programming
Once you learn the fundamentals of programming, your coding life is easy.
Let me show you some interesting quotes from the book- The Almanack of
Naval Ravikant(ISBN-13: 978-1544514222):
from the basics. If you can’t rederive concepts from the basics
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
V. Sarcar, Test Your Skills in C# Programming
https://doi.org/10.1007/978-1-4842-8655-5_4
Theoretical Concepts
This section includes the theoretical questions and answers on classes and
objects.
Class and Object
4.T1 What do you mean by a class and an object?
Answer:
A class is a template or blueprint, and an object is an instance of that.
An object can have states and behaviors. For example, if you are familiar
with the game of football (or soccer, as it’s known in the United States), you
can say that Ronaldo or Beckham are objects from the Footballer class.
You may notice that they have states like “playing state” or “nonplaying
state.” In the playing state, they can show different skills (or, behaviors)—
they can run, they can kick, they can pass the ball, and so forth. To begin
with object-oriented programming, you can ask the following questions:
What are the possible states of my objects?
What are the different functions (behaviors) that they can perform in
those states?
It is interesting to note that now you can type less to create an object in
C#. Here the type Sample is apparent. So, you can use the following line
to create an object and get the same output:
Constructor
4.T3 Why do you need constructors? How do you use them in your
program?
Answer:
We use constructors to initialize objects. Here are some important points
about them:
The class name and corresponding constructor’s names must be the same.
They do not have any return types.
You can say that there are two types of constructors: parameterless
constructors (sometimes referred to as constructors with no argument or
default constructor ) and constructors with parameters (termed
parameterized constructors ). In C# parlance, it does not matter whether
you create a parameterless constructor or if it is created by the C#
compiler. In both cases, we generally call it a default constructor.
You can also distinguish constructors based on whether it is a static
constructor or nonstatic constructor (or instance constructor). You’ll see
the use of instance constructors in this chapter. We use instance
constructors to initialize instances (objects) of the class, whereas static
constructors are used for initializing the class itself when it comes into
the picture for the first time. I’ll discuss the meaning of static in Chapter
9 of this book.
In general, common tasks like initialization of all the variables inside a
class are achieved through constructors.
4.T4 How can you differentiate a constructor from a method?
Answer:
There are two important things to remember:
A class and its constructors have the same name.
Constructors do not have a return type.
4.T5 “Constructors do not have a return type.” Using this
statement, did you mean that their return type is void?
Answer:
No. Implicitly a constructor’s return type is the same as its class type.
You should not forget that even void is considered a return type.
4.T6 I am a little bit confused about the use of a user-defined
parameterless constructor and a C#-provided default constructor. Both
appear the same to me. Is there any key difference between them?
Answer:
In C# parlance, it does not matter whether you create your
parameterless constructor or if it is created by the C# compiler. In both
cases, we generally call it the default constructor . Sometimes both may
appear to be the same. But with a user-defined constructor, you can have
some flexibility; you can use your own logic and have some additional
control on object creation.
Consider the following code segment:
Sample sample = new Sample();
Console.WriteLine($"The variable _flag is
initialized with {sample._flag}.");
class Sample
{
internal int _flag;
internal Sample()
{
this._flag = 10;
}
}
Compile and run this program. You’ll get the following output:
Now comment out (or remove) the constructor. Compile and run the
program again. This time you’ll see the following output:
You can recognize that in this case, the variable _flag is initialized
with the default value of an int.
Let us refer to the Microsoft documentation in this context (see
https://docs.microsoft.com/en-
us/dotnet/csharp/programming-guide/classes-and-
structs/instance-constructors ). Refer to the following
points:
If a class has no explicit instance constructors, C# provides a
parameterless constructor that you can use to instantiate an instance of
that class.
That parameterless constructor initializes instance fields and properties
according to the corresponding initializers. If a field or property has no
initializer, its value is set to the default value of the field’s or property’s
type.
If you declare at least one instance constructor in a class, C# doesn’t
provide a parameterless constructor.
If you are familiar with inheritance, then you will also understand the
importance of the following point, which I picked up from the C# language
reference:
If the class is abstract, then the declared accessibility for the default
constructor is protected. Otherwise, the declared accessibility for the
default constructor is public.
So, you understand that for a C#-provided default constructor, you
cannot change the access modifier, but for a user-defined constructor, you
can choose the access modifier too.
Bonus Q&A
4.T7 I often hear about objects and instances. Are they the same?
Answer:
Yes. We often use the terms object and instance interchangeably.
4.T8 What is the use of the this keyword?
Answer:
Sometimes you need to refer to the current object, and to do that, you
use the keyword this. It is because the this reference refers to the
instance itself. To understand this better, consider the following code and
discussion:
class Sample
{
internal int id; // instance variable
public Sample(int sampleId) // sampleId is a
local variable
{
id = sampleId;
}
}
You are familiar with code like this: a=25. Here you are assigning 25
into a. But are you familiar with code like this: 5=a;? No. The compiler
will raise an issue; it also assumes that the left-hand side of an assignment
is a variable.
In the previous example, sampleId was our local variable (these are
seen inside methods, blocks, or constructors), and id was our instance
variable (these are declared inside a class but outside a method, block, or
constructor).
So, instead of the sampleId, if you use id, you need to tell the
compiler about your direction of the assignment. It should not be confused
about “which value is assigned where.” Here I am assigning the value of the
local variable to the instance variable, and the compiler should clearly
understand the intention. With this.id=id;, the compiler will clearly
understand that the instance variable id should be initialized with the value
of the local variable id.
Suppose, by mistake, you wrote something like id=id; in the previous
scenario. Then there will be confusion from the compiler’s point of view.
That’s because, in that case, it interprets that you are dealing with the same
two local variables. (Though your intention was different, you meant that
the id on the left side is the field and the other one is the method
parameter.) In this case, you’ll see the following warning:
CS1717 Assignment made to same variable; did you
mean to assign something else?
I hope you understand the use of the this keyword. In short,
remember that a variable in a smaller scope hides the variable in a larger
scope (we call it name hiding). The this keyword helps you resolve this
kind of scenario. It is a special reference to the current instance and helps
you write better readable code.
4.T9 How should you choose among private, public, and internal
accessibility levels?
Answer:
The general guideline is that you should make everything as restricted
as possible, which means you should prefer private over internal,
and internal over public, but still allow everything to work properly.
Let me give you an elaborate answer: you should protect the class data
in general. So, you make class-level variables private in most cases.
How can you decide between public and internal? Well, you ask yourself
the question: how do you want to reuse the class? If you want your class to
be used only inside a single project, it should be internal. But if you
want it to be reused across many projects, you need to mark it with the
public keyword.
You can apply the same rule for methods as well.
4.T10 Can we say that class is a custom type?
Answer:
Yes. When you’re familiar with memory management, you’ll also say
that a class is a reference type .
4.T11 What are the different types of members that a class can
have?
Answer:
A class can contain different kinds of members such as fields, methods,
constructors, finalizers, constants, properties, events, operators, indexers,
and nested types.
Author’s Note: As mentioned, in this chapter, you’ll see some of these
types. The usage of the remaining members will be discussed in subsequent
chapters.
4.T12 It appears to me that we can call some methods to initialize
those variables. Then why do we choose constructors?
Answer:
From that point of view, to do that job, you need to call the method
explicitly; i.e., in simple language, the call is not automatic. But with
constructors, we are performing automatic initialization each time we are
creating objects.
4.T13 Which initializations occur first, field initializations or
initialization through a constructor?
Answer:
Field initializations occur first. But when a constructor assigns the value
of a field, it overwrites any value given during field declaration.
Programming Skills
Using the following questions you can test your programming skills in C#.
Note that if I do not mention the namespace, you can assume that all parts
of the program belong to the same namespace.
}
}
Answer:
Explanation:
The field values were initialized first. So, you can see that the initial
values of the variables i and d were 5 and 0, respectively. There was no
initial value for d; so, this program considered the default value for a
double data type. Later, inside the constructor, you changed these values
(incremented i by 2 and d by 1). So, the final instance values appeared as 7
and 1, respectively.
It is interesting to note that in C# 7.1, you write something like the
following to initialize a variable with the default value of its type:
internal double d= default ;
4.P2 Predict the output of the following code segment:
class Sample
{
internal int _i;
internal Sample(int i)
{ this._i = i; }
}
Answer:
This program will not compile . You will see the following error:
Explanation:
The error message is self-explanatory. The only question that may come
into your mind is: where is the default parameterless constructor that C#
provides automatically for you? The answer is: if you do not supply any
explicit instance constructor in a class, then only C# supplies you with a
parameterless constructor to instantiate an instance of that class. (You can
refer to https://docs.microsoft.com/en-
us/dotnet/csharp/programming-guide/classes-and-
structs/instance-constructors .) But in this case, you are
specific: you are using a constructor that can accept an integer argument.
So, the compiler expects you to supply the required argument.
4.P3 Predict the output of the following code segment:
sample._flag=5
sample._number=10
Explanation:
Inside the client code, you used a parameterless constructor. But notice
that this constructor calls the second constructor with an argument 5. This
argument value sets the _flag value now . The _flag value is then
multiplied by 2 to set the value of the _number.
Console.WriteLine("Employee Details:");
Console.WriteLine($"Name: {emp1.Name}, Id:
{emp1.Id}");
Console.WriteLine($"Name: {emp2.Name}, Id:
{emp2.Id}");
Console.WriteLine($"Name: {emp3.Name}, Id:
{emp3.Id}");
class Employee
{
public string Name;
public int Id;
Employee Details:
Name: Anonymous, Id: 0
Name: Bob, Id: 0
Name: Sumit, Id: 2
Explanation:
Here you have used the concepts of optional parameters in a
constructor. This constructor needs two arguments: one for the employee’s
name and one for the employee’s Id. If you pass a fewer number of
arguments, the compiler will not complain at all. This is because this
application can pick the default values that are already set in the optional
parameter’s list.
For example, notice the second line of output: you can see the default
values of an employee object were Anonymous and 0 (corresponding to
the employee’s name and ID). But in the next line, you can see that the
program picked the employee name as Bob, but since there was no ID
associated with this object, it supplied the default ID of 0. The final line of
this output confirms that when you pass all the required arguments, the
program can accept all those values.
4.P5 Can you compile the following code?
Employee Details:
Name: Bob, Id: 1
Name: Kate, Id: 3
Explanation:
This program demonstrates the use of object initializers . You can see
that a single line of code was sufficient to instantiate the objects emp1 and
emp2. You have also experimented with the different types of constructors
in this program. But it is evident that in all cases, object initializers simplify
the instantiation proces s.
class Sample
{
int _flag;
internal Sample(int i = 0)
{
this._flag = i;
}
}
Answer:
No. You will see the following error:
Explanation:
In a namespace, internal is the default access modifier for a class,
but the class members have private visibility. In this context, the
Microsoft documentation ( https://docs.microsoft.com/en-
us/dotnet/csharp/programming-guide/classes-and-
structs/access-modifiers ) says the following:
So, in this case, if you can make the instance variable internal or
public , there is no compile-time error. You have seen the use of the
internal instance variables already in different code segments in this
chapter .
Nested Classes
4.P7 Predict the output from the following code segment :
class Sample
{
public int Flag1;
public Sample()
{
Flag1 = 1;
Console.WriteLine("Sample is
initialized.");
}
public class NestedSample
{
public int Flag2;
public NestedSample()
{
Flag2 = 2;
Console.WriteLine("NestedSample is
initialized.");
}
}
}
Answer:
NestedSample is initialized.
Flag2=2
Explanation:
You saw an example of a nested class in C#.
Additional Note:
The public field inside the NestedSample class is named Flag2
following Microsoft’s naming conventions for the public members of a
type. If interested, you can refer to
https://docs.microsoft.com/en-
us/dotnet/csharp/fundamentals/coding-style/coding-
conventions for your reference.
4.P8 Can you compile the following code?
Console.WriteLine("---4.P8---");
Sample.NestedSample nested = new();
class Sample
{
class NestedSample
{
public NestedSample()
{
Console.WriteLine("NestedSample is
initialized.");
}
}
}
Answer:
No. You will see the following error :
Explanation:
In 4.P6, you have seen already that in a namespace, internal is the
default access modifier for a class, but the class members including nested
classes and structs have private access by default.
_flag1=6
_flag2=1
Explanation:
This code uses a default constructor that can accept an optional
argument. When you do not pass the argument, it sets the flag values 5 and
0, respectively, and invokes the instance method Increment() to
increase these values by 1.
4.P10 Predict the output of the following code segment:
Answer:
This program can compile and run successfully. You will receive the
following output:
Result: 15.7
}
Answer:
You will receive the following compile-time error:
Explanation:
The error message is self-explanatory. So, you cannot use var for
instance variables.
4.P12 Predict the output of the following code segment:
class Sample
{
internal double _flag;
private Sample()
{
_flag = 5.75;
}
}
Answer:
You will receive the following compile-time error:
Explanation:
The constructor is private, so you cannot create an object using new().
This is useful when you want to prevent inheritance or you need to follow
the Singleton design pattern. (A Singleton design pattern ensures that a
class can have a maximum of one instance.)
So, is there any way to access the _flag variable ? Yes. If you are
familiar with the static keyword, you can use the following code to
access the _flag variable of Sample:
Sample sample=Sample.GetInstance();
Console.WriteLine($"sample._flag =
{sample._flag}");
class Sample
{
internal double _flag;
private Sample()
{
_flag = 5.75;
}
public static Sample GetInstance()
{
return new Sample();
}
}
sample._flag = 5.75
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
V. Sarcar, Test Your Skills in C# Programming
https://doi.org/10.1007/978-1-4842-8655-5_5
5. Inheritance
Vaskaran Sarcar1
Theoretical Concepts
This section includes theoretical questions and answers on inheritance.
Basic Concepts
5.T1 What do you mean by inheritance?
Answer:
The main objective of inheritance is to promote reusability and
eliminate redundancy of code. By using this concept, you can reuse a class
by expanding it into a more specific type of that class. As a result, this child
class can obtain the features/characteristics of its parent class. In
programming terms, you say that a child class is derived from another class,
called its parent class (or base class). Therefore, the parent class is placed
at a higher level in the class hierarchy.
In general, you deal with four types of inheritance.
Single inheritance: A child class is derived from a single base class.
Hierarchical inheritance: Multiple child classes can be derived from a
single base class.
Multilevel inheritance: The parent class has a grandchild.
Multiple inheritance: A child can derive from multiple parents.
POINTS TO REMEMBER
C# does not support multiple inheritance through class; that is, a child
class cannot derive from more than one parent class. To deal with this
type of situation, you can use interfaces.
There is another type of inheritance known as hybrid inheritance. It is
a combination of two or more types of inheritances.
Types of Inheritance
5.T2 Can you demonstrate each type of inheritance with code
examples?
Answer:
For your reference , here I summarize each type of inheritance.
Single Inheritance
A child class is derived from one parent class. Figure 5-1 shows this
type of inheritance.
Figure 5-1 Single inheritance
Here is some sample code:
Hierarchical Inheritance
Multiple child classes can be derived from one parent class. Figure 5-2
shows this type of inheritance.
Figure 5-2 Hierarchical inheritance
Here is some sample code:
Multilevel inheritance
The parent class can have a grandchild. Figure 5-3 shows this type of
inheritance.
Figure 5-3 Multilevel inheritance
Here is some sample code:
class Parent
{
// Some code
}
class Child : Parent
{
// Some code
}
Multiple Inheritance
A child can derive from multiple parents. But this type of inheritance is
not supported in C# through classes. You need to learn about interfaces.
Figure 5-4 shows this type of inheritance.
You can see that there are two different types of errors: CS0122 and
CS1061.
CS0122: This indicates that the private member _flag1 from class A is
inherited in child class B.
CS1061: You also examined this case study with a field that was not
present in this class hierarchy (i.e., the field is not present—neither in A
nor in B). When you tried to access the member with a B-type object, you
encountered a different error. Therefore, if _flag1 is absent in class B,
then you should get a similar error.
5.T6 Why doesn’t C# support multiple inheritance through classes?
Answer:
The main reason is to avoid ambiguity. It can cause confusion in typical
scenarios; for example, let’s suppose that you have a method named
Show() in your parent class. Let us further assume that the parent class
has multiple children—Child1 and Child2, who are redefining (in
programming terms, overriding) the method as per their needs. The code
may look like the following:
class Parent
{
public void Show()
{
Console.WriteLine("I am in Parent");
}
}
Author’s Note: You will see the discussions on delegates in Chapter 10.
Programming Skills
Using the following questions, you can test your programming skills in C#.
Note that if I do not mention the namespace, you can assume that all parts
of the program belong to the same namespace.
Fundamentals
5.P1 Predict the output of the following code segment :
class Parent
{
public Parent()
{
Console.WriteLine("The parent class
constructor is
called.");
}
}
Explanation:
The constructor’s calls follow the path from the parent class to the child
class.
Author’s Note:
If you are aware of the expression-body definitions in C#, you know
that I could rewrite the previous code as follows:
class Parent
{
public Parent()=> Console.WriteLine("The
parent class
constructor is
called.");
}
Note The expression-body definitions help you write concise and more
readable code. Many entry-level programmers are unsure about them.
So, in this book, I have used a mixed approach: in some code segments
they are present, and in other segments they are absent. This approach
can help you understand both types of code.
B obB=new B();
Console.WriteLine($"x={obB.x}");
Console.WriteLine($"y={obB.y}");
class A
{
internal int x = 1;
}
class B : A
{
internal int y = x + 2;
}
Answer:
This program will not compile. You will see the following error:
Author’s Note: There are many discussions on this topic. The statement
y=x+2; in the preceding example is equivalent to y=this.x+2; this
means the current object. So, if we want to make a call like this.x, the
current object needs to be completed first. But the current object may not be
completed at this point in some situations (e.g., if x is a property, instead of
a field, that is not created yet or it is a part of another instance, etc.). You
should also remember that the constructors are created to handle this kind
of initialization. So, if these types of constructs are allowed, they can also
question the purpose of constructors.
I believe that it’s worth reading the following MSDN resources:
https://blogs.msdn.microsoft.com/ericlippert/2008
/02/15/why-do-initializers-run-in-the-opposite-
order-as-constructors-part-one/
https://blogs.msdn.microsoft.com/ericlippert/2008
/02/18/why-do-initializers-run-in-the-opposite-
order-as-constructors-part-two/
5.P3 Predict the output of the following code segment:
class Parent
{
protected int a;
public Parent() { }
public Parent(int a)
{
this.a = a;
}
}
Explanation:
You have seen one usage of the base keyword where you use it to
invoke the parent class constructor. It is useful to decide which base class
constructor should be invoked before you create an instance of the derived
class.
5.P4 Predict the output of the following code segment:
class Person
{
protected string ssn = "123-45-6789";
protected string name = "K. Peterson";
}
Answer:
Name: K. Peterson
SSN: 123-45-6789
ID: E001
Explanation:
You have seen another usage of the base keyword where you use it to
access the base class method that is overridden by a derived class method.
POINTS TO REMEMBER
As per the language specification, base class access is permitted only
in a constructor, an instance method, or an instance property access.
The keyword base should not be used in a static method context.
It is similar to the super keyword in Java and the base keyword in
C++. It is almost identical to C++’s base keyword, from which it was
adopted. However, in Java, there is a restriction. You can refer to
https://docs.oracle.com/javase/tutorial/java/Ia
ndI/super.xhtml where it says that the invocation of a superclass
constructor must be the first line in the subclass constructor.
Instead of using base.GetInfo(), you could use GetInfo() to
produce the same output in this program. Why? Notice that there is
only one method named GetInfo() in this inheritance hierarchy.
Console.WriteLine("Case-Study: 5.P5");
sealed class Parent
{
public void ShowClassName()=>
Console.WriteLine("Inside the Parent
class");
class Polygon
{
public Polygon() => Console.WriteLine("Polygon
class
constructor.");
}
Answer:
Yes. This code will produce the following output:
Polygon class constructor.
Square class constructor.
Explanation:
Notice that the sealed keyword is applied to the Square class, but
not to the Polygon class. So, you can inherit the Polygon class, but not
the Square class. I also believe that you remember the calling sequence of
the constructors. So, when you instantiate a Square instance, the base
class constructor was called first. This is why you could use the following
line inside the derived class to get the same output:
class Polygon
{
sealed int _flag = 1;
sealed Polygon()=> Console.WriteLine("Polygon
class
constructor.");
}
Answer:
No, you’ll see two error messages of the same kind:
Explanation:
You cannot apply the sealed keyword to a field or a constructor. You
have also learned that constructors are not inherited at all .
Method Overloading
5.P8 Predict the output of the following code segment :
Sample sample = new Sample();
Console.WriteLine(sample.Add(2, 3));
Console.WriteLine(sample.Add("Mike", "Proctor"));
class Sample
{
public int Add(int x, int y)
{
return x + y;
}
public string Add(string s1, string s2)
{
return string.Concat(s1,",",s2);
}
}
Answer:
5
Mike,Proctor
Explanation:
You have seen an example of method overloading in the Sample class
definition.
Additional Note:
Using the expression-body definitions in C#, you can rewrite the
previous code as follows (note that the return keyword is absent in the
expression body definition):
class Sample
{
public int Add(int x, int y) => x + y;
class Sample
{
public int Sum(int x, int y)
{
return x + y;
}
public double Sum(int x, int y)
{
return x + y;
}
}
Answer:
No, you’ll see the following error:
Explanation:
The compiler does not consider the “return type” to differentiate these
methods. Earlier (see 5.T16) I mentioned the MSDN reference, which says
the following:
class Employee
{
public Employee() =>
Console.WriteLine("Employee name: Unknown,
ID: Not given");
Explanation:
You have seen an example of constructor overloading now.
Author’s Note: What is the difference between a method and a
constructor? A constructor has the same name as a class, and also it has no
return type. So, you can consider a constructor as a special kind of method
that has the same name as a class and no return type. But there are many
other differences: the main focus of a constructor is to initialize objects. You
cannot call them directly.
5.P11 Can you compile the following code?
Explanation:
I have used one of the latest features of C# top-level statements. So, the
previous code segment is equivalent to writing the following:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Overloading the Main()
method.");
Main(5);
static void Main(int a)
{
Console.WriteLine("I am inside Main(int
a) now.");
}
}
}
Answer:
You can compile and run this code, but you’ll see this warning message:
Explanation:
You can refer to the latest documentation at
https://docs.microsoft.com/en-
us/dotnet/csharp/fundamentals/program-
structure/top-level-
statements#:~:text=CS7022%20The%20entry%20point%20
of,one%20or%20more%20Main%20methods , where you can see
the following information:
class Sample
{
public Sample()
{
// Some code
}
public void Sample()
{
// Some code
}
}
Answer:
No. In C#, it is not allowed. You’ll receive the following error:
In this context, Java programming lovers can note that Java (tested in
JDK 17.0.1) allows the following code to compile and execute:
POINT TO REMEMBER
If possible, try to be consistent with parameter names and their
corresponding order for overloaded methods. For example, the following
code shows an example of a good design:
public void ShowMe(int a) {..}
public void ShowMe(int a, int b){…}
Note that in the second method, the position of int a is the same as
in the first method.
The following code is not recommended:
public void ShowMe(int a) {..}
public void ShowMe(int x, int b){…} // Try to use ‘a’ instead of ‘x’
Note that in the second method, you start with int x instead of
int a .
Method Overriding
5.P14 Predict the output of the following code segment :
class Parent
{
public virtual void ChangeMe()
{
Console.WriteLine("Initial version of the
ChangeMe().");
}
Explanation:
The presence of the virtual keyword in the base/parent class
indicates that you can change the method to behave differently inside a
derived class using the override keyword. You can interpret this as
follows: the virtual keyword in the base class says “You can provide an
alternative implementation of this method,” while the override keyword
in the derived class says “You gave me permission, so I’m going to take this
opportunity and apply a change to it as per my needs.”
class Parent
{
public void DoNotChangeMe()
{
Console.WriteLine("Initial version of the
DoNotChangeMe().");
}
}
Explanation:
The error message is self-explanatory. You understand that since you
have not marked the parent class method with the virtual keyword, you
cannot override it inside the Derived class.
5.P16 Can you compile the following code?
class Parent
{
public void ShowMe() =>
Console.WriteLine("The ShowMe() of
Sample.");
}
class Derived : Parent
{
public void ShowMe() =>
Console.WriteLine("The ShowMe() of
Derived.");
}
Answer:
This code did not use either the virtual or override keyword.
Still, this code can compile and run to produce the following output:
The ShowMe() of Derived.
But you’ll see a warning message saying this:
class Parent
{
public virtual void ShowMe() =>
Console.WriteLine("The ShowMe() of the
parent
class.");
}
Answer:
Yes. The C# compiler allows you to compile and run this program, and
it can produce the following output:
class Parent
{
public virtual int GetNumber(int i)
{
Console.WriteLine("I am inside the Parent
class.");
return i;
}
}
Answer:
No. You’ll receive the following compile-time error:
Explanation:
The method signatures of the Parent class method and Child class
method are not the same. So, you cannot use the override keyword for
the Child class method.
5.P19 Predict the output of the following code segment:
class Parent
{
public virtual int GetNumber(int i) => i;
}
}
Answer:
This code can compile and run successfully. You’ll get the following
output:
Value1:5
Value2:7
Value3:12
Explanation:
This code segment shows you an example where you see the concept of
method overloading and overriding together. You can see that
GetNumber(int i) and GetNumber(int i, int j) are
overloaded inside the Child class. In addition, you can see that the method
GetNumber(int i) is overridden inside the Child class.
5.P20 Can you compile the following code?
Console.WriteLine("---5.P20---");
class Parent
{
public virtual int GetNumber(int i)=> i;
}
Answer:
No. You’ll receive the following compile-time error:
Console.WriteLine("Case study-(5.P21)");
class Parent
{
internal virtual int GetNumber(int i) => i;
Answer:
No. You’ll receive the following compile-time error:
POINT TO REMEMBER
The previous program segments show you that the return types (see
5.P21), method parameters (see 5.P18), and access specifiers (see 5.P20)
of virtual and overridden methods must be the same; otherwise, you’ll
see the compile-time errors.
Method Hiding
5.P22 Can you predict the output?
Console.WriteLine("-----.");
Child child = new Child();
Console.WriteLine(child.GetNumber(5));
Console.WriteLine(child.GetNumber(5, 7));
class Parent
{
internal virtual int GetNumber(int i) => i;
public virtual int GetNumber(int i, int j) =>
i + j;
}
Explanation:
This program shows you the use of the virtual, override, and
new keywords. Notice that you can also choose a different access modifier
for a method inside the Child class when you use the new keyword. We’ll
discuss this program again (see 6.P1) in the next chapter when you see me
using polymorphic code .
Note In the next chapter, we’ll discuss the polymorphic code and
investigate the use of the virtual, override, and new keywords in
that context.
Console.WriteLine("Experimenting covariance.");
Parent parent = new Parent();
Console.WriteLine($"The flag value in the parent
class
is: {parent.GetFlag(5).Flag}");
parent = new Child();
Console.WriteLine($"The flag value in the child
class
is:
{parent.GetFlag(5).Flag}");
Experimenting covariance.
The flag value in the parent class is: 5
The flag value in the child class is: 10
Explanation:
In an older version of C#, you can see the following error in a similar
context:
Did you notice that in 5.P21 you got a compile-time error, because you
used different return types (int and double)? But this program does not
show any compile-time error. Why? Because from C# 9 onward, the
covariant return type is supported. To learn more about this, you can refer to
https://docs.microsoft.com/en-
us/dotnet/csharp/language-
reference/proposals/csharp-9.0/covariant-returns .
In short, covariance enables you to use a more derived type (i.e., more
specific) than the originally specified type, while contravariance reverses
the scenario, which means that it enables you to use a less derived type (i.e.,
less specific).
In C#, covariance and contravariance enable implicit reference
conversion for array types, delegate types, and generic type arguments.
(You’ll see a discussion of them in Chapter 10 when I discuss delegates in
detail.) You can find Microsoft’s documentation for it at
https://docs.microsoft.com/en-
us/dotnet/csharp/programming-
guide/concepts/covariance-contravariance/ .
5.P24 Can you compile the following code?
public Parent()
{
Flag = 5;
}
Explanation:
In this context, the contravariant parameter type is not supported yet.
The reason for this is discussed at
https://github.com/dotnet/csharplang/discussions/3
562 . But you may note that a generic interface or generic delegate type
can have both covariant and contravariant type parameters. You’ll learn
more about them in Chapter 10.
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
V. Sarcar, Test Your Skills in C# Programming
https://doi.org/10.1007/978-1-4842-8655-5_6
6. Polymorphism
Vaskaran Sarcar1
Theoretical Concepts
This section includes the theoretical questions and answers about
polymorphism.
Polymorphism
6.T1 What do you mean by polymorphism?
Answer:
Polymorphism is a Greek word. We use it to mean one name with many
forms. Consider the behavior of a pet dog. When it sees an unknown person,
it is angry and starts barking a lot. But when it sees its owner, it makes
different noises and behaves differently. In both cases, this dog sees with his
eyes, but based on the observation, he behaves differently.
Polymorphic code can work in the same way. To illustrate this, let us
assume that you hear me saying there is a method that can add some
operands. What will your immediate interpretation be? The answer is
simple: if the operands are integers, you expect to get a sum of the integers.
But if you deal with string operands, you expect to get a concatenated
string.
You can also consider popular search engines such as Google, Microsoft
Bing, or Yahoo in this context. You know that they all do the same basic
task (i.e., search) in their own way.
Polymorphism can be of two types.
Compile-time polymorphism : The compiler knows which method to
invoke in which situation once the program is compiled. It is also known
as static binding or early binding.
Runtime polymorphism : The actual method calls are resolved at runtime,
but at compile time, you cannot predict which method will be invoked
when the program runs. Consider a simple use case: suppose you
generate a random number at the very first line when you execute a
program. Let’s assume that if the generated number is an even number,
you will call a method, Method1( ), which prints “Hello”; otherwise,
you’ll call a method whose name is the same but it prints “Hi.” Now,
you’ll agree that if you execute the program, then only you can see which
method is invoked (i.e., the compiler cannot resolve the call at compile
time). You do not have any clue as to whether you will see “Hello” or
“Hi” prior to the program’s execution. This is why sometimes it is also
termed as dynamic binding or late binding.
6.T2 Can you give an example of polymorphic code?
Answer:
Before I show you a code example , you need to remember the
following points:
Polymorphic code allows you to use derived classes that implement the
same method in different ways. This means that these classes can do the
same task in a custom way.
You mark a base class method with the virtual keyword. You know
that a virtual method can be overridden in a derived class. So, using
the virtual keyword, you indicate that a derived class can provide an
alternative implementation of the parent class method as per its needs.
When a derived class does this, it also marks the method with the
override keyword.
The Tiger and Dog are two derived classes of the Animal class in the
upcoming program. Inside the Animal class, there is a virtual method
called Sound(). The subclasses of the Animal class override this
method as per their needs.
Understand that when you use Dog dog = new Dog();, we say that
you are programming to an implementation. Notice that in this case the
reference (variable) type and object both are of the same type.
Now comes the interesting part: instead of writing something like Dog
dog =new Dog(), you can write Animal animal=new Dog();
and the C# compiler is OK with this. Why? It is because the Dog class is
inherited from the Animal class. So, it allows you to use them anywhere
an Animal object is required. (You can understand the reason behind
this kind of design: both tigers and dogs are animals, but the reverse is
not necessarily true.)
So, when the Common Language Runtime (CLR) sees the following
code:
}
class Tiger: Animal
{
public override void Sound()
{
Console.WriteLine("Tigers roar.");
}
}
class Dog : Animal
{
public override void Sound()
{
Console.WriteLine("Dogs bark.");
}
}
This code produces the following output:
6.T3 I can see that a parent class variable (reference) can point to a
child class object, but I get a compile-time error for the reverse
scenario. Here is a sample code segment with comments:
Note As per the C# language reference, you can apply the abstract
modifier to classes, methods, properties, indexers, and events.
6.T5 Can you tag a method with both the abstract and sealed
keywords?
Answer:
No. This is like if you say that you want to explore C# but you will not
learn through any material. Similarly, by declaring abstract, you want to
share some common information across the derived classes, and you
indicate that overriding is necessary for them (that is, the inheritance chain
needs to grow), but at the same time, by declaring sealed, you want to
put an end on the derivational process so that the inheritance chain cannot
grow. Here you are trying to implement two opposite constraints
simultaneously.
Interface
6.T6 What is an interface?
Answer:
An interface defines a specific set of functionalities without specifying
how to implement them. In simple words, an interface defines a contract.
Let us consider a real-world example: smartphones in today’s world. You
know that a smartphone has a touch screen. It is an interface that a user uses
to dial a number or connect to the Internet (using the mobile hotspot). Now,
think carefully about the following points in this example:
A smartphone user knows how to dial a number but may not know “how”
it works.
When the user purchases an upgraded model of this smartphone, the user
expects to see a similar interface to get a better service (such as a high-
speed connection and faster Internet service).
Similarly, in C# programming, you have a construct called an interface
that plays the same role as mentioned in the previous example. The
Microsoft documentation ( https://docs.microsoft.com/en-
us/dotnet/csharp/fundamentals/types/interfaces ) says
the following:
interface IAnimal
{
void Sound();
}
class Tiger : IAnimal
{
public void Sound()
{
Console.WriteLine("Tigers roar.");
}
}
POINT TO NOTE
You may note an important change: you can mention accessibility
modifiers for interface members. So, the following code will compile in
.NET 6 (which supports C# 10):
interface ISomeInterface
{
protected void Show(); // Error in C# 7.3
}
But you’ll get the following errors in C# 7.3:
CS8703 The modifier 'protected internal' is not valid for this item in
C# 7.3. Please use language version '8.0' or greater.
CS8707 Target runtime doesn't support 'protected', 'protected
internal', or 'private protected' accessibility for a member of an interface.
6.T7 Can you tell me the difference between an abstract class and an
interface?
Answer:
Here are some differences:
In C#, instance fields are not allowed in an interface, but they can be
present in an abstract class .
An interface can inherit from another interface. But it cannot inherit from
an abstract class. On the contrary, an abstract class can inherit from
another abstract class or another interface. For your immediate reference,
I provide you with the following code segment with supporting
comments:
interface IParent1 { }
abstract class Parent2 { }
interface IChild1 : IParent1 { } // OK
abstract class Child2 : Parent2, IParent1 { } //
OK
interface IChild3 : Parent2 { } // Error CS0527
The last line of the previous code segment raises the following compile-
time error:
interface IMarkerInterface
{
In this case, the implementing class does not need to define any method
because the interface itself does not have any such method. Developers may
opt for a marker interface for the following reasons:
To create a common parent.
If a class (or a structure) implements an interface, then the instance of it
implicitly converts to the interface type.
But Microsoft suggests you avoid using it and use attributes instead.
The documentation at https://docs.microsoft.com/en-
us/dotnet/fundamentals/code-analysis/quality-
rules/ca1040 says the following:
“If your design includes empty interfaces that types are expected to
implement, you are probably using an interface as a marker or a
way to identify a group of types. If this identification will occur at
run time, the correct way to accomplish this is to use a custom
attribut e.”
Programming Skills
By using the following questions, you can test your programming skills in
C#. Note that if I do not mention the namespace, you can assume that all
parts of the program belong to the same namespace.
Basic Concepts
6.P1 Can you predict the output?
Console.WriteLine("-----.");
parent = new Child();
Console.WriteLine(parent.GetNumber(5));
Console.WriteLine(parent.GetNumber(5, 7));
class Parent
{
internal virtual int GetNumber(int i)=> i;
public virtual int GetNumber(int i, int j)=> i
+ j;
}
class Child : Parent
{
internal override int GetNumber(int i)=> i + 2;
Explanation:
If you compare this code with 5.P22 in Chapter 5, you’ll see that these
are very similar, but this time you can see a change in the output (notice the
last line). The first two lines of output are straightforward. But wait! This
time you have used the concept of polymorphism too. This is why the
parent class reference is used to point to a subclass object. So, take a look at
the following code:
Console.WriteLine(parent.GetNumber(5));
Console.WriteLine(parent.GetNumber(5, 7));
When the Parent class reference points to the Child class object and
then uses the following code:
parent.GetNumber(5);
it calls the overridden method from the Child class. But if you use the
following code:
parent.GetNumber(5, 7);
it does not call the newly defined method from the Child class. If you
want to invoke the method GetNumber(int i, int j) from the
Child class, you can use the following lines (similar to P5.22 in Chapter
5):
Here is the summarized code segment with comments for your easy
reference:
POINTS TO REMEMBER
In C#, all methods are nonvirtual by default. But, in Java, they are
virtual by default. So, you need to tag the keyword override to
avoid any unconscious overriding.
C# also uses the new keyword to mark a method as nonoverriding.
I remind you that with C# 9.0+ onward, for an apparent type, you can
simplify the new expression. For example, instead of using Parent
parent = new Parent() , you can use Parent parent =
new();, and you can refer to the following link in this context:
https://docs.microsoft.com/en-
gb/dotnet/fundamentals/code-analysis/style-
rules/ide0090 .
class Vehicle
{
public virtual void ShowCommonFeature()
{
Console.WriteLine("Inside
Vehicle.ShowCommonFeature");
}
}
class Bus : Vehicle
{
public override void ShowCommonFeature()
{
Console.WriteLine("Inside
Bus.ShowCommonFeature");
}
public void ShowSpecialFeature()
{
Console.WriteLine("Inside
Bus.ShowSpecialFeature");
}
}
Answer:
No. You’ll receive the following compile-time error:
Explanation:
The inheritance mechanism allows a child class to add new methods
inside its class definition. There is no issue with this, but a problem occurs
when you use a parent class reference to invoke a child-specific method.
This is because a parent class is created before its child classes. So, a parent
class does not have any idea about its child classes. This is why a parent
class reference has a restrictive view. So, you need to be careful in similar
contexts.
6.P3 Predict the output of the following code segment:
Explanation:
You have seen another common usage of the base keyword. Using this
approach, you override a method by doing everything the old method did,
and then you add something more to it.
Answer:
This code will compile, and it’ll produce the following output:
Explanation:
You have seen a common usage of an abstract class. Notice that the
derived class- CompletedHome provides the complete implementation of
the ShowStatus() method .
6.P5 Can you compile the following code?
class Shape
{
public void About()
{
// Some code
}
public void ShowArea()
{
// Some code
}
public abstract void SpecialFeature();
Answer:
No. In this case, you’ll see the following error:
Explanation:
If a class contains an abstract method, the class itself is incomplete. So,
you need to mark the Shape class with the abstract keyword.
6.P6 Can you compile the following code?
Answer:
No. In this case, you’ll see the following error:
}
class Monkey : Animal
{
public Monkey(string color) : base(color) =>
Console.WriteLine("It becomes a monkey.");
Explanation:
This code shows the following points:
An abstract class can contain fields. (Notice the color parameter.)
A derived class must provide the complete implementation for the
abstract method that was defined in the abstract class. (Notice the
Jump() method.)
A derived class can use a concrete method that is defined in the abstract
class. (Notice the Run() method.)
Following the construction initialization order, the base class is
instantiated before the derived class.
6.P9 Can you compile the following code?
}
class Polygon : Shape
{
public override void AboutMe()=>
Console.WriteLine("Polygon");
}
Answer:
No. You’ll see the following error:
Explanation:
The simple formula is as follows: if you want to create objects of a
class, the class needs to be complete; that is, it should not contain any
abstract methods. So, if the child class cannot provide implementation (i.e.,
body) of all the abstract methods, it should mark itself again with the
keyword abstract, like in the following:
}
abstract class Polygon : Shape
{
public override void AboutMe()=>
Console.WriteLine("Polygon");
}
6.P10 Can you compile the following code?
}
}
Answer:
No. You’ll see the following error:
}
public void AboutMe()
{
// Some code
}
}
Answer:
You cannot mark a constructor with the abstract or sealed
keyword in C# programming. You’ll see the following error in this case:
Answer:
This code will compile and produce the following output:
Interface case studies.
Tigers roar.
Dogs bark.
Explanation:
You have seen how to use an interface and polymorphic code.
6.P13 Can you compile the following code?
Console.WriteLine("Case study-6.P13");
interface IAnimal
{
void Sound();
void Run();
}
class Tiger : IAnimal
{
public void Sound()=> Console.WriteLine("Tigers
roar.");
}
Answer:
No. So, in this case, you’ll see the following error:
Explanation:
A class needs to implement all the methods of an interface; otherwise, it
needs to mark itself with the abstract keyword. If you do not want to
provide the implementation of the Run() method in the Tiger class, here
is a possible fix:
}
class BengalTiger : Tiger
{
public override void Run() =>
Console.WriteLine("Bengal tigers run
fast.");
}
Answer:
Yes. This code can compile and run to produce the following output:
Tigers roar.
Bengal tigers run fast.
Explanation:
The previous error description is self-explanatory. When a class extends
from another class (provided it is not sealed or there are no other similar
constraints) and an interface, you need to remember the positional notations
. The parent class is positioned first, followed by a comma, followed by the
interface name(s), such as in the following:
class ChildClass:
BaseClass,IInterface1,IInterface2
{
// Some code
}
}
Answer:
Yes. This code can compile and run to produce the following output:
Car details:
Has an air conditioner? True
Number of wheels: 4
Explanation:
This example shows that you can implement the concept of multiple
inheritance using interfaces in C#. Probably you have also noticed that I
have used a simplified new() statement. So, the following line of code
SuperCar car = new (); also compiled.
6.P18 Can you compile the following code?
interface IVehicle
{
void Describe();
}
class Car : IVehicle
{
void IVehicle.Describe()
{
Console.WriteLine("This is a car.");
}
}
Answer:
Yes. This code can compile and run to produce the following output:
This is a car.
Explanation:
Here I have written some polymorphic code. In addition, you have seen
an explicit interface implementation . Note that in an explicit interface
implementation, the method name is preceded by the interface name, such
as <interfacename>.methodname (){...} .
6.P19 Can you compile the following code?
interface IVehicle
{
void Describe();
}
interface ICompany
{
void Describe();
}
Explanation:
The Microsoft documentation at
https://docs.microsoft.com/en-
us/dotnet/csharp/programming-
guide/interfaces/explicit-interface-implementation
says the following:
((IVehicle)car).Describe();
((ICompany)car).Describe();
// Case study-6.P20
interface IVehicle
{
void Describe();
}
interface ICompany
{
void Describe();
}
Explanation:
You saw the power of explicit interface implementations when the
interfaces have methods with the same signature. In such a case, you may
not want to invoke the same implementation for both interfaces.
To achieve this, you call a class member through the specified interface.
We name the class member by prefixing it with the name of the interface
and a period.
6.P21 Can you compile the following code?
}
Answer:
Yes. This code can compile and run to produce the following output:
Explanation:
You have seen a way to invoke a default interface method (in C# 8.0
onward, this is allowed).
6.P22 Can you compile the following code?
Console.WriteLine("Experimenting a default
interface
method.");
Car car = new();
car.Describe();
interface IVehicle
{
internal void Describe()=>
Console.WriteLine("The XYZ company makes
this car.");
}
class Car : IVehicle
{
Answer:
No. This time you’ll see the following error:
CS1061 'Car' does not contain a definition for
'Describe' and no accessible extension method
'Describe' accepting a first argument of type
'Car' could be found (are you missing a using
directive or an assembly reference?)
Explanation:
The Microsoft documentation (see
https://docs.microsoft.com/en-
us/dotnet/csharp/programming-
guide/interfaces/explicit-interface-implementation
) says the following about this kind of error:
interface IVehicle
{
internal void Describe() =>
Console.WriteLine("The XYZ company makes
this car.");
}
class Car : IVehicle
{
internal void Describe()
{
Console.WriteLine("Overriding the default
interface
method.");
// Some other code-if any
}
void IVehicle.Describe()
{
Console.WriteLine("Explicitly overriding
the default
interface method.");
// Some other code-if any
}
}
6.P23 Can you compile the following code?
Answer:
No. You’ll see the following error:
Explanation:
You must remember that an interface can inherit from one or more base
interfaces, but not from a class. A class or structure can have some
implementations. So, if you allow an interface to inherit from them, the
interface may contain the implementations, which is against the core aim of
an interface.
6.P24 Can you compile the following code?
Console.WriteLine("Can an interface contain
fields?");
interface ISomeInterface
{
int _flag;
}
Answer:
No. You’ll see the following error:
Explanation:
The Microsoft documentation confirms this by saying the following
(you can refer https://docs.microsoft.com/en-
us/dotnet/csharp/fundamentals/types/interfaces ):
interface ISomeInterface
{
abstract void Show();
Answer:
Starting with C#8.0, this code can compile and run. But in the earlier
editions, you’ll see errors. For example, if you use this interface definition
in .NET Framework 4.5, you’ll see the following compile-time error:
CS8703 The modifier 'abstract' is not valid for
this item in C# 7.3. Please use language version
'8.0' or greater.
6.P26 Can you compile the following code?
Answer:
If you run this code prior to C# 8, you’ll see the compile-time error. But
now it is a correct behavior. If interested, you can refer to
https://github.com/dotnet/docs/issues/28427 .
One C# 11 Feature
6.P27 Can you compile the following code?
Answer:
If you run this code prior to C# 10, you’ll see the compile-time error.
Here is a sample:
Note In Chapter 15, you’ll see how to test preview features. There
you’ll see some of the new features too.
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
V. Sarcar, Test Your Skills in C# Programming
https://doi.org/10.1007/978-1-4842-8655-5_7
Theoretical Concepts
This section includes the theoretical questions and answers on data
encapsulation and related topics in C#.
7.T1 How is abstraction different from encapsulation?
Answer:
Abstraction focuses on the noticeable behavior of an object, and
encapsulation focuses on the implementation part of that behavior.
Encapsulation helps you to bundle your data, and at the same time, it can
hide some information that you do not want to disclose to the outside world.
So, the key purpose of abstraction is to show only the essential details
and hide the background details of implementation. Abstraction is also very
much related to encapsulation, but the difference may be easily understood
with a simple day-to-day scenario: when you press a button on our remote
control to switch on the television (TV), you may not know about the
internal circuits of the TV or how the remote control enables a TV. You
simply know that different buttons on the remote control have different
functionalities, and as long as they work properly, you are happy. So, a user
is isolated from the complex implementation details that are encapsulated
within the remote control (or TV). At the same time, the common
operations that can be performed through a remote control can be thought
of as an abstraction of the remote control.
7.T2 What do you mean by a property in C#? How is it useful?
Answer:
A property is a member that provides a flexible mechanism to read,
write, or compute the value of a private field. Initially, properties may
appear similar to fields, but they have either a get block or a set block, or
both blocks. These special blocks represent some methods that are called
accessors. But this construct helps us to replace any GetX or SetX method
using a simpler syntax, without needing to publicly expose the data. In
simple terms, get blocks are used for reading purposes, and set blocks are
used for assigning purposes.
Note When a property has only the get accessor, you call it a read-only
property. If the property contains only the set accessor, you call it a
write-only property. Normally, you see a property with both accessors,
and that kind of property is known as a read-write property.
In addition to this type of control and flexibility, you can impose some
constraints and validations using the properties, and these characteristics
make them unique. You can also take actions based on the validation rules
such as raising an event or notification. Finally, the properties in C# are
important and useful because they help encapsulate an object’s state.
7.T3 Why do you impose so much importance on the private data of
a class?
Answer:
Following the encapsulation principle , a class should protect the
integrity of its data. In general, an “unwritten but well-known rule” for an
experienced developer is that a class should keep its internal data private
but can allow a public method to read the current state of that data. If
needed, the developer can also allow a user to set a new value for that data
using another public method. This enables a class to act as a gatekeeper that
allows only the acceptable changes to it.
7.T4 I am confused: how a class can act as a gatekeeper when it
allows making a change to its data?
Answer:
You are allowed to make reasonable changes only. To avoid an
unwanted change, you can provide constraints inside the methods to
validate whether a change request is reasonable/valid. If required, you can
also perform some additional computations on the data before you store the
data or return it. You know that properties allow those kinds of methods
using some simple syntax. (You can consider 7.P3 in this context.)
Creating a Property
7.T5 How can you demonstrate the use of properties in C#?
Answer:
This is a sample program . Here you see a Game class that has a read-
write property named Score. I have kept some comments for your easier
understanding.
Console.WriteLine("Properties.Demo-1.");
Game game = new();
// game._score = 10;//Error CS0122: it is
inaccessible
// Setting a new value
game.Score = 70; // Ok.
// Reading the value
Console.WriteLine($"Current score:{game.Score}");
class Game
{
private int _score; // The "backing" field
public int Score // The public property
{
get
{
return _score;
}
set
{
_score = value;
}
}
}
When you execute this program, you will see the following output:
Properties.Demo-1.
Current score:70
POINTS TO REMEMBER
The contextual keyword value is an implicit parameter associated
with properties. You typically use it for assignment.
Sometimes the private field that stores the data exposed by a public
property is called a backing store or a backing field. So, _score is
the private backing field in the preceding example.
You can mark a property as public, private, internal,
protected, protected internal, or private
protected. You can also mark it with other keywords such as
virtual, abstract, new, override, sealed, or static.
Starting from C# 9, we can use the init keyword to define an
accessor method in a property (or indexer). The init-only setters can
assign a value to the property (or the indexer) element only during
object construction.
class Game
{
private int _score; // The "backing" field
public int Score // The public property
{
get => _score;
set => _score = value;
}
}
I have not used any validation logic inside the get or set accessor. So,
using the auto-implemented properties, you can further simplify this code as
follows:
class Game
{
public int Score
{
get;set;
}
}
The previous code segment creates a backing field behind the scenes.
This is a commonly used technique to create simple properties. But in this
case, you cannot have direct access to the backing field.
You can also set a default value in the previous code segment. Here is a
code snippet:
class Game
{
public int Score
{
get; set;
} = 60;
}
Author’s Note: When you download the source code from the Apress
website, you can see the complete programs in the Demo_7.T5,
Demo_7.T5B, and Demo_7.T5C folders.
7.T6 Why should I prefer public properties rather than public
fields?
Answer:
You can promote encapsulation, which is one of the key features of
OOP.
7.T7 When should you prefer read-only properties?
Answer:
One possible use case is to create immutable types.
7.T8 What are the different ways to create an immutable property?
Answer:
Immutability is a big topic. But the documentation at
https://docs.microsoft.com/en-
us/dotnet/csharp/programming-guide/classes-and-
structs/how-to-implement-a-lightweight-class-with-
auto-implemented-properties summarizes it nicely:
Declare only the get accessor, which makes the property immutable
everywhere except in the type’s constructor.
Declare an init accessor instead of a set accessor, which makes the
property settable only in the constructor or by using an object initializer.
Declare the set accessor to be private. The property is settable within
the type, but it is immutable to consumers .
Creating an Indexer
7.T9 What is an indexer? Why are they useful?
Answer:
Indexers are similar to properties, except their accessors can take
parameters.
Using indexers, you can use instances of a class (or struct) to be indexed
like arrays.
These index values can be set or retrieved easily without specifying the
type or instance member. Here are some useful notes for you.
The keyword this is used to define the indexer. So, you’ll find that the
following program (see 7.T10) is similar to properties, but the key
difference is that the name of the property is this.
7.T10 How can you create an indexer in C#?
Answer:
Here is a simple program to demonstrate this:
Console.WriteLine("***Indexer Demo.***");
Animals animals = new();
Console.WriteLine("Here are the animals:");
Console.WriteLine(animals[0]);
Console.WriteLine(animals[1]);
class Animals
{
private string[] _names;
public Animals()
{
_names = new string[] { "Tiger", "Lion" };
}
public string this[int index]
{
get
{
string temp = String.Empty;
if (index >= 0 && index <
_names.Length)
{
temp = _names[index];
}
else
{
// You can throw an error
}
return temp;
}
set
{
if (index >= 0 && index <
_names.Length)
{
_names[index] = value;
}
else
{
// You can throw an error
}
}
}
}
Once you compile and run this program, you will see the following
output:
***Indexer Demo.***
Here are the animals:
Tiger
Lion
The updated list of the animals:
Cat
Lion
POINTS TO REMEMBER
You can apply different modifiers such as private, public,
protected, internal, protected internal, or private
protected to an indexer.
The return type can be a valid C# data type.
You can create a type with multiple indexers, each with different types
of parameters.
As usual, you can create read-only indexers by eliminating the set
accessor. Though it is syntactically correct, some developers like to
use a method in those scenarios (e.g., it is always good to use a
method to retrieve employee information corresponding to the
employee ID). So, I normally avoid this:
Class Employee{
// Using indexers to get employee details
public string this[int empId]
{
get
{
//return Employee
details
}
}
}
7.T11 Can we declare a property or an indexer inside of an interface?
Answer:
Yes. The overall concept is the same: you are allowed to provide an
explicit or an implicit interface implementation in those cases. You’ll see
some code examples in 7.P13, 7.P14, and 7.P15.
Programming Skills
By answering the following questions, you can test your programming
skills in C#. Note that if I do not mention the namespace, you can assume
that all parts of the program belong to the same namespace.
Basic Concepts
7.P1 Predict the output of the following code segment :
Console.WriteLine("Experimenting properties in
C#.");
Game game = new();
Console.WriteLine($"Current level: {game.Level}");
game.Level = 2;
Console.WriteLine($"Current level: {game.Level}");
class Game
{
private int _level;
public Game() { _level = 1; }
public int Level
{
get => _level;
}
}
Answer:
This code can compile and run successfully. You’ll get the following
output:
Explanation:
You have seen a simple example of a read-write property named Level
inside the Game class. This example also demonstrates the usage of the
expression-bodied members and a simplified new() expression.
7.P2 Can you compile the following code?
Console.WriteLine("Experimenting properties in
C#.");
Game game = new();
game.Level = 2;
class Game
{
private int level;
public int Level
{
get => level;
}
}
Answer:
No. You’ll receive the following compile-time error:
Explanation:
Remember the following guidelines from the Microsoft documentation
(see
https://docs.microsoft.com/en-
us/dotnet/csharp/programming-guide/classes-and-
structs/how-to-implement-a-lightweight-class-with-
auto-implemented-properties ) that states the following:
When you declare a private set accessor, you cannot use an object
initializer to initialize the property. You must use a constructor or a
factory method.
class Game
{
private int level=1;
public int Level
{
get => level;
set
{
if (value < 0 || value> 2)
{ level = 0; }
else { level = value; }
}
}
}
Answer:
This code can compile and run successfully. You’ll get the following
output:
Current level: 1
Current level: 2
Current level: 0
Explanation:
This is an example where constraints are applied to a property. You can
see that when I tried to set the level of the game to greater than 2, the
program logic considers setting the new level as 0.
7.P4 Can you compile the following code?
class Game
{
public int Level
{
get; set;
} = 2;
}
Answer:
Yes, this code can compile and run successfully. You’ll get the
following output:
Explanation:
The Level here is an auto-implemented property. I set an initial value
of 2.
class Shape
{
public virtual double Area
{
get => 0;
}
public override string ToString()
{
return "unknown shape";
}
}
class Circle : Shape
{
readonly int _radius;
public Circle(int radius)
{
this._radius = radius;
}
public int Radius
{
get => _radius;
}
public override double Area
{
get => 3.14 * _radius * _radius;
}
public override string ToString()
{
return "circle";
}
}
Answer:
Here is the output:
}
Answer:
Here is the output:
Explanation:
This program shows how to use an abstract property. In addition, the
shape variable is used polymorphically.
Author’s Note: I said earlier that you can create a property with
different types of modifiers. In this section, we have experimented with two
of them: virtual properties and abstract properties.
class Game
{
private string _name;
public Game() { _name = "SuperGame"; }
public string Name
{
get => _name;
}
}
Answer:
This code can compile and run successfully. You’ll get the following
output:
Explanation:
You have seen a simple usage of the init accessor for the property
called Name. Here I use a single statement to assign a value; so, I was able
to implement the init accessor as an expression-bodied member.
7.P8 Can you compile the following code?
}
}
Answer:
No. You’ll receive the following compile-time error that is self-
explanatory:
7.P9 Can you predict the output of the following code segment?
class Game
{
private string _name;
public Game() { _name = "Car Simulator"; }
public string Name
{
get => _name;
}
}
Answer:
This code can compile and run successfully. You’ll get the following
output:
Answer:
Yes, this code can compile and run successfully. You’ll get the
following output:
Using Indexers
7.P11 Can you compile the following code?
Console.WriteLine("***Indexer Demo.***");
Animals animals = new();
Console.WriteLine("Here are the animals:");
Console.WriteLine(animals[0]);
Console.WriteLine(animals[1]);
class Animals
{
private string[] _names;
public Animals()
{
_names = new string[] { "Tiger", "Lion" };
}
public string this[int index]
{
get => _names[index];
}
}
Answer:
This code can compile and run successfully. You’ll get the following
output:
***Indexer Demo.***
Here are the animals:
Tiger
Lion
Explanation:
This program shows that both the get and set accessors can be
implemented as expression-bodied members. This has been allowed since
C# 7.0.
7.P12 Suppose, in the previous program, I replaced the following
line:
Console.WriteLine(animals[1]);
Console.WriteLine(animals[2]);
Author’s Note: You need to be careful when you deal with indexed
values in programs like this. For example, without proper validation, the
following line will also cause a similar error:
string IEmployee.Name
{
get => _name;
set => _name = value;
}
int IEmployee.Id
{
get => _id;
set => _id = value;
}
}
Answer:
This code can compile and run successfully. You’ll get the following
output:
class Employee
{
string _name;
int _id;
public Employee(string name, int Id)
{
this._name = name;
this._id = Id;
}
}
interface IEmployee
{
public string this[int id] { get; }
public int this[string name] { get; }
}
class EmployeeStore : IEmployee
{
Employee[] employees;
public EmployeeStore()
{
employees = new Employee[] {
};
}
public string this[int id]
{
get => employees[id].Name;
}
public int this[string name]
{
get
{
int temp = 0;
if (name.Equals("Sam")) temp =
employees[0].EmployeeId;
if (name.Equals("Kate")) temp =
employees[1].EmployeeId;
if (name.Equals("Jack")) temp =
employees[2].EmployeeId;
return temp;
}
}
}
Answer:
Yes, this code can compile and run successfully. You’ll get the
following output:
Bonus Q&A
7.P16 Can you compile the following code? If so, can you predict the
output?
class Employee
{
string _name;
int _id;
public Employee(string name, int Id)
{
this._name = name;
this._id = Id;
}
}
interface IEmployee
{
public bool this[string name, int id] { get; }
}
class EmployeeStore : IEmployee
{
Employee[] employees;
public EmployeeStore()
{
employees = new Employee[] {
};
}
public bool this[string name, int id]
{
get
{
bool temp = false;
if (name.Equals("Sam") && id == 1)
temp = true;
if (name.Equals("Kate") && id == 2)
temp = true;
if (name.Equals("Jack") && id == 3)
temp = true;
return temp;
}
}
}
Answer:
Yes, this code can compile and run successfully. You’ll get the
following output:
class Rectangle
{
double length, breadth;
public Rectangle(double length, double
breadth)
{
this.length = length;
this.breadth = breadth;
}
public double Area
{
get => length * breadth;
}
}
Answer:
Yes, this code can compile and run successfully. You’ll get the
following output:
Explanation:
This code demonstrates that a property can be computed from other
data. So, in this case, the area of the rectangle is computed from the
length and breadth of the rectangle.
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
V. Sarcar, Test Your Skills in C# Programming
https://doi.org/10.1007/978-1-4842-8655-5_8
8. Handling Exceptions
Vaskaran Sarcar1
When you write code for an application, you expect that it will execute as per your plan, but
you will often encounter sudden surprises. These surprises may occur through some
careless mistakes. For example, you have implemented the wrong logic, or you have
ignored some loopholes in the code paths of the program. However, many of the failures are
beyond the control of a programmer. Developers call these unwanted situations exceptions.
Handling these exceptions is essential when you write an application. This chapter focuses
on exception handling and covers the following topics:
Exceptions and their uses in C# programming
Use of the try, catch, and finally blocks
Use of multiple catch blocks in a program
Use of a general catch block
Throwing and rethrowing exceptions
Use of exception filters
Custom exception class and its usage
Let us review and test your understanding of these topics now.
Theoretical Concepts
This section includes the theoretical questions and answers on the exception handling
mechanism in C# programming.
Basic Concepts
8.T1 What do you mean by exceptions in C#?
Answer:
These are C#’s built-in error handling mechanisms. Exceptions help you deal with any
unexpected or exceptional situations that occur when a program is running. In simple terms,
you can think of an exception as an event that breaks the normal execution flow of a
program.
When exceptional situations arise, an exception object is created and thrown into the
method that created the exception. That method may or may not handle the exception. If it
cannot handle the exception, it will pass the responsibility to another method. (Similar to
our daily life, when a situation goes beyond our control, we seek advice from others.) If
there is no method to take responsibility for handling a particular exception, an error dialog
box appears (indicating an unhandled exception), and the execution of the program stops.
8.T2 Why is exception handling important?
Answer:
When you write a program, you can classify the possible errors into the following broad
categories:
Compile-time errors
Runtime errors
Compile-time errors are comparatively easy to fix because you know about them much
earlier (i.e., at compile time). These may occur mainly due to syntax errors or typing
mistakes. The compiler can easily point to them so that you can take corrective measures
immediately.
On the contrary, runtime errors are dangerous because you encounter them only when a
program was being executed. Since the program is compiled early, you expect it to be run
successfully, but the problem may arise due to some typical cases such as you are trying to
divide a number by 0, open a file, or connect to a database using a wrong password or
incorrect implementation of logic. As a result, this type of error is tough to fix and is costly.
C#’s exception handling mechanism is important because it helps you deal with these
typical runtime errors in advance.
8.T3 What are the common keywords in C#’s exception handling mechanism?
How are they used?
Answer:
You can use the following keywords to deal with C# exceptions: try, catch, throw,
and finally. In addition, starting with C# 6.0, you can see the use of the contextual
keyword when in a catch statement to filter an exception. Here I summarize their usage:
You can guard an exception using a try-catch block. The code that may throw an
exception is placed inside a try block, and this exceptional situation is handled inside a
catch block.
The code in the finally block must execute. In general, this block is placed after a
try block or a try-catch block.
When an exception is raised inside a try block, the control jumps to the respective
catch or finally block. The remaining part of the try block will not be executed.
You can associate multiple catch blocks with a try block. In this case, you must place
them from the most specific to the least specific types.
To illustrate the previous bullet point, I show you a sample code segment that uses three
catch blocks. These are used to handle different types of exceptions. Here you’ll notice
the following points:
The catch(ArithmeticException ex) block is placed after
catch(DivideByZeroException ex){...}.
Also, catch(Exception ex){...} is placed after all other catch blocks.
The DivideByZeroException class inherits from the ArithmeticException
class, which inherits from the SystemException class, which again inherits from the
Exception class.
Now go through the following code segment to review your understanding:
In addition, anyone can raise an exception with the throw keyword . Here is some
sample code for you:
try
{
// Some code before
throw new IndexOutOfRangeException("Index is out of
range.");
}
// Remaining code skipped
Finally, as mentioned, you can use exception filters that allow you to catch an exception
when a certain condition is met. 8.P10 in this chapter shows you a complete example of
this. For now, let me pick a particular code segment to demonstrate this:
catch (Exception ex) when (ex.Message.Contains("Timeout"))
{
Console.WriteLine($"Caught: " + ex.Message);
}
Author’s Note: All C# exceptions are runtime exceptions. So, there is no concept of
compile-time checked exceptions. This is why the throws keyword is absent in C#, but it
is present in Java.
8.T4 What is the base class for all exceptions in C#?
Answer:
System.Exception is the base class for all exceptions in C#.
8.T5 Why is analyzing a stack trace important in exception handling?
Answer:
In many cases , when you invoke a method, that method may call another method that
may cause an exception. In that situation, the CLR will look for a catch block that can
handle the exception, and it will execute the first such catch block that it finds. So,
analyzing the stack trace of an exception is important to understand the real cause of the
problem, and you will often use the StackTrace property of System.Exception in
this context.
8.T6 How is the finally block used?
Answer:
The finally block will always execute. It does not matter whether an exception is
thrown. These blocks are typically used to write the cleanup code such as closing a file or
releasing resources. A finally block can execute in any of the following situations:
After a try block ends
After a catch block ends
After the control leaves, the try block (due to a typical reason such as there is a
return statement or an exception raised in this block)
A few things can still defeat the purpose of the finally block such as if the program
encounters an infinite loop or you have written a bad code segment something like the
following:
System.DivideByZeroException
System.FormatException
System.ArithmeticException
System.InvalidCastException
System.NullReferenceException
System.OutOfMemoryException
System.IndexOutOfRangeException
System.NotImplementedException
8.T8 I understand that I can suppress an error using the exception handling
mechanism. Also, if I encounter an exception, I can rethrow a different exception. Is
this correct?
Answer:
Yes, but it is never recommended. However, when you learn to create a custom
exception, you can combine this original exception with the custom exception message and
then rethrow it for better readability.
Exception Filters
8.T9 Why do you use exception filters?
Answer:
An exception type may not be fine-grained enough. For example, consider a case when
you encounter a WebException. You understand that this error can be a result of various
reasons, such as if there is a protocol error, a timeout error, or a different error. Based on
these possible reasons, you may take different actions to handle a situation. The exception
filters help you in such a context. For example, in this case, you can introduce different
catch blocks with different filters to provide the best action as follows:
try
{
// Some code that can raise a WebException
}
catch (WebException ex) when (ex.Status ==
WebExceptionStatus.Timeout)
{
//some code
}
Custom Exception
8.T10 Write a program to create a custom exception and show its usage .
Answer:
The following program demonstrates the use of a custom exception. Before I show you
the complete example, I’d like you to note the following guidelines from Microsoft (refer to
https://docs.microsoft.com/en-
us/dotnet/standard/exceptions/how-to-create-user-defined-
exceptions ):
You can create a custom exception class by deriving it from the Exception class.
When we create a custom exception, the class name should end with the word
Exception.
Supply the three overloaded versions of constructors (as described in the upcoming
example).
There is an additional guideline in this context (see
https://docs.microsoft.com/en-
us/dotnet/api/system.applicationexception?
redirectedfrom=MSDN&view=net-6.0 ):
“You should derive custom exceptions from the Exception class rather than the
ApplicationException class. You should not throw an ApplicationException
exception in your code, and you should not catch an ApplicationException exception
unless you intend to rethrow the original exception.”
Let’s get back to the example. In this program, I assume that if a divisor is 1 or less,
we’ll throw an exception. Here is the complete program where
SmallDivisorException is a custom exception class that has three common
constructors:
Thank you!
Explanation:
You can see that I have used the second overloaded version of the constructor. If you
want to use the default constructor (which was commented on earlier), there is a different
message (which I’ve shown in bold):
Thank you!
Programming Skills
By using the following questions, you can test your programming skills in C#. Note that if I
do not mention the namespace, you can assume that all parts of the program belong to the
same namespace.
Fundamentals
8.P1 Predict the output of the following code segment :
Answer:
You’ll get the following output:
Explanation:
You can verify the following points from this program:
When an exception is raised inside a try block, the control jumps to the respective
catch block. The remaining part of the try block does not execute.
The code in the finally block always executes even though we encounter an
exception .
Answer:
Here are the outputs as per the user inputs. I have made certain lines bold to highlight
the difference in the output messages.
Case-Study-1: User enters 234
Answer:
You’ll receive the following compile-time error:
Explanation:
Exceptions follow the inheritance hierarchy, and you need to place catch blocks
accordingly. Let us examine the Microsoft documentation at
https://docs.microsoft.com/en-us/dotnet/api/system.exception?
view=net-6.0#TryCatch . Here you find the following:
A catch block handles an exception of type T if the type filter of the catch block
specifies T or any type that T derives from. The system stops searching after it finds
the first catch block that handles the exception. For this reason, in application
code, a catch block that handles a type must be specified before a catch block
that handles its base types…
It continues:
Figure 8-1 The DivideByZeroException class inherits from the ArithmeticException class.
Figure 8-2 The ArithmeticException class inherits from the SystemException class.
Figure 8-3 The SystemException class inherits from the Exception class.
Now you understand that to avoid this compile-time error, you need to place these
catch blocks as follows:
In general, you use these variables to get some useful information such as printing the
exception message or tracking a stack trace. When you do not use them, you can avoid
declaring them inside the catch blocks. However, I do not recommend this practice,
because the exception messages or stack traces are very useful to understand the exceptions.
But I show this demonstration to remind you that the following block (or a similar block
where you do not mention the exception variable) does not raise any compile-time error:
// Also OK
catch (DivideByZeroException)
{
// Some code
}
int a = 100, b = 0;
try
{
Console.WriteLine($"The result of a/b is :{a / b}");
}
catch (Exception ex)
{
Console.WriteLine($"The exception caught: {ex.Message}");
}
catch
{
Console.WriteLine("Caught a non-CLS exceptions.");
}
Answer:
Yes, this code can compile and run successfully. You’ll get the following output:
Explanation:
The output of the previous program is straightforward. But I use this example to
demonstrate a variety of catch blocks. Notice the final catch block. It is often called a
general catch block.
Why is this important? As per the language specification, a catch clause that does not
name an exception class can handle any exception. Also, some exceptions do not derive
from System.Exception. These are called non-CLS exceptions. Some .NET
languages, including C++/CLI, support these exceptions. In C#, you cannot throw non-CLS
exceptions, but you can catch them in the following ways:
Option 1: Use a catch (RuntimeWrappedException e) block.
Option 2: Use the catch{} block and place it after all the other catch blocks.
Option 1 is useful when you need to access the original exception, but option 2 is useful
when you do not need to access the exception details (consider the case when you want to
log the details without worrying about anything else).
By default, a Visual C# assembly catches non-CLS exceptions as wrapped exceptions.
You can use the RuntimeWrappedException.WrappedException property to
process the exception. This is why even though this program compiles and executes, you
will see the following warning message:
Author’s Note: For more information on this topic, you can refer to
https://docs.microsoft.com/en-us/dotnet/csharp/how-to/how-to-
catch-a-non-cls-exception .
8.P5 Will the following code segment compile?
int a = 100, b = 0;
try
{
Console.WriteLine($"The result of a/b is :{a / b}");
}
catch
{
Console.WriteLine("Caught a non-CLS exceptions.");
}
catch (Exception ex)
{
Console.WriteLine($"The exception caught: {ex.Message}");
}
Answer:
No. You’ll receive the following compile-time error:
Explanation:
In 8.P4 I mentioned the Microsoft documentation, which states that you need to place
the general catch block after all other catch blocks .
}
catch (Exception ex)
{
Console.WriteLine($"The exception caught: {ex.Message}");
Answer:
You’ll get the following output:
Explanation:
This program shows that you can explicitly throw an exception using C#’s throw
statement.
8.P7 Predict the output of the following code segment:
}
class NumberMaker
{
int[] _numbers = { 1, 2, 3 };
}
}
Answer:
You’ll get the following output:
Confused? Let’s run the program and see the output. I have made the important change
in bold.
***The case study on the throw keyword.***
numbers[0]=1
You can use the throw e syntax in a catch block to instantiate a new exception
that you want to pass on to the caller. In this case, the stack trace of the original
exception, which is available from the StackTrace property, is not preserved.
Explanation:
The Microsoft documentation says that starting with C# 7.0, this code can compile and
run successfully. This is why I can use the following statement without a compile-time
error:
int c = b > 0 ? (a / b) :
throw new DivideByZeroException("b becomes
0");
Author’s Note: You saw the use of the throw expression with the conditional operator
in 8.P9. You can use it with other C# constructs as well such as an expression-bodied
method or a null-coalescing operator.
Filtering Exceptions
8.P10 Does the following code segment compile?
using System.Net;
Explanation:
In this scenario, the when clause acts as a filter: if a WebException is thrown and
the Boolean condition (which is followed by when) is true, then only a corresponding catch
block will handle that exception. Using this kind of filter, we can catch the same exception
but handle it in a different catch block.
Author’s Note: You’ll want to use this technique when a particular exception object
can point to multiple errors. In a real-world application, each error can be associated with
an error code. Following this demonstration, you can use this error code to handle a
particular error inside a catch clause .
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
V. Sarcar, Test Your Skills in C# Programming
https://doi.org/10.1007/978-1-4842-8655-5_9
9. Useful Concepts
Vaskaran Sarcar1
Before you move to Part III of this book, let’s review some other useful
concepts in C#. This will help you remove many common doubts and
enhance your programming skills. This chapter covers the following topics:
Type conversions including boxing and unboxing
Static data
Extension methods
Value types and reference types in C#
The difference between passing value-type by value and passing value-
type by reference
A quick overview of pointers in C#
Use of the const and readonly keywords in a program
Let us review these topics and test your understanding now.
Theoretical Concepts
This section includes the theoretical questions and answers on various
topics in C#.
Type Conversions
9.T1 Why do you use casting? How can you differentiate different types
of casting?
Answer :
Casting helps you convert one data type to another. Often it is called
type conversion. Basically, there are two types of casting: implicit and
explicit.
As the name suggests, implicit casting is automatic, and you do not
need to worry about it. But, you need cast operators for explicit casting.
In implicit casting , the conversion path follows small to large integral
types, or derived types to base types. For example, the following segment of
code will compile and run perfectly:
int a = 1;
// Implicit casting
double b = a; // OK
Explicit casting considers the reverse case. If you write something like
this:
double d = 12.5;
int c = d;// ERROR CS0266
To avoid this error, you can apply an explicit casting like the following:
// Explicit casting
int c = (int)d; // OK
Note Notice that I used the word object type. You may remember that
in C#, all types( predefined and user-defined), value types and reference
types inherit directly or indirectly from System.Object and the
object type is an alias for System.Object.
Using boxing, a value type allocates an object instance on the heap and
then boxes (stores) the copied value into that object. Here is an example of
boxing:
int i = 1;
object o = i; // Boxing
Now consider the reverse scenario and look at the last line in the
following code segment. If you try to write code like this:
int i = 1;
object o = i; // Boxing
// int j = o; // ERROR CS0266
you will get the compile-time error that says the following:
int i = 1;
object o = (object)i;
// object o=i; // It is OK, because since boxing
is implicit.
9.T3 Can you create a top-level static class in C#? Also, can you
mention some of the C# constructs where you can apply the static
keyword?
Answer:
The answer to the first part of the question is yes. The following code
will not raise any compile-time error:
To answer the second part of this question, refer the following summary
from Microsoft ( https://docs.microsoft.com/en-
us/dotnet/csharp/language-
reference/keywords/static ):
C# Types
9.T6 What are the different types in C#?
Answer:
C# types can be broadly classified into the following categories:
Value types
Reference types
Pointer types
Generic types
You can see the use of value types and reference types throughout this
book. The generic types are discussed in Chapter 13.
Author’s Note: Pointer types are commonly used in unmanaged code
(for example, interoperability with C APIs). A detailed discussion of them is
beyond the scope of this book.
9.T7 Give some examples of value types in C#.
Answer:
As mentioned in Chapter 3, we often refer to the bool type, char type,
integral, and floating-point numeric types as simple types. All the simple
types are built-in value types in C#. A value type can be further classified
into the following categories: structure types and enumeration types. All
simple types are structure types.
Author’s Note: Remember that string is a built-in reference type.
9.T8 Can you give some examples of reference types in C#?
Answer:
The built-in object, dynamic, and string types belong to the reference
type. You often see the use of the class, interface, and delegate
keywords to declare a reference type. Beginning with C# 9, you can use the
record keyword also to define a reference type.
9.T9 How do the value types differ from the reference types?
Answer:
The fundamental difference between these types is the way they are
handled inside the memory. Microsoft (
https://docs.microsoft.com/en-
us/dotnet/csharp/language-
reference/keywords/reference-types ) summarizes the
difference nicely as follows:
Unsafe Code
9.T10 I often hear about the safe code and unsafe code. What do these
terms mean?
Answer:
Up until now, you have seen safe code only. This type of code does not
allocate raw memory or directly use pointers to access a memory location.
Since .NET tools can verify the safety of code, Microsoft calls them
verifiably safe code.
But in an “unsafe” context, you can write unverifiable code using
pointers. Here you can allocate or deallocate a memory block and use
function pointers. Since the .NET tools cannot verify the safety issues of
these operations, this code is called unverifiable code or unsafe code .
Note The key takeaway is that “unsafe” code is risky, but not
necessarily dangerous. It often increases the performance of the
application. Unsafe code is also useful when your code calls a native
function that requires pointers.
int a = 100;
unsafe
{
int* p;
p = &a;
Console.WriteLine("Pointer Type Demo.");
Console.WriteLine($"*p is {*p}");
}
If you see the following error:
CS0227 Unsafe code may only appear if compiling
with /unsafe
you know that you need to follow the Visual Studio IDE’s suggestion to
compile this program. When you work with the unsafe code, you can apply
the setting shown in Figure 9-1 (see the arrow).
Now you can run this program and get the following output:
Additional Notes:
For the declaration int* p, the expression *p denotes the int
variable found at the address contained in p. Here is some additional
information:
You cannot convert between a pointer type and an object.
Pointers do not inherit from objects.
Boxing and unboxing are not applicable for pointers.
To declare multiple pointers at the same place, the * is written with the
underlying type, such as
int* a, b, c; // OK
Extension Methods
9.T12 What is an extension method in C#?
Answer:
An extension method tells that you can “add” a custom method to an
existing type without creating a new derived type, recompiling, or
otherwise modifying the original type. These are static methods but called
with instance method syntax.
In 9.P5, 9.P6, 9.P7, and 9.P8, you’ll see a detailed discussion on this
topic with examples.
Constants in C#
9.T13 How do you deal with constants in C#?
Answer:
In this context, C# supports two special keywords , const and
readonly, to prevent the modification of a field. Still, they have some
different characteristics.
First, you need to understand that you can have two types of constants:
compile-time constants and runtime constants. You use the const keyword
for compile-time constants. But you need to be aware of the following
restrictions before you use the const keyword :
You can apply const to the built-in types (except System.Object)
only. At the time of this writing, you cannot use them for user-defined
types, including class, struct, and arrays.
You cannot apply them to a method, an event, or a property.
You can initialize a constant field only at the declaration.
On the contrary, for the runtime constants, you should use the
readonly keyword. You can assign to a readonly field multiple times.
You must initialize them in the field declaration or inside a constructor. But
you cannot assign a new value to the field once the control comes out from
the constructor.
9.T14 What are the advantages of using constants in the program?
Answer:
They are easy to read and modify. You can modify the variable in a
single place to reflect the changes across the program. Otherwise, you need
to find out each occurrence of the variable across the program. You can
understand that this alternative approach is not recommended at all, because
it is tedious and error-prone.
9.T15 When should you prefer readonly over const ?
Answer:
In 9.T13, I already told you that readonly helps you deal with a
runtime constant. In addition, you can apply readonly to a static as well
as a nonstatic variable, whereas compile-time constants are always static.
So, using the readonly modifier, different instances of a class can have
different values for a field. To understand this, let us consider the following
program :
Console.WriteLine(new Sample()._flag2);
Console.WriteLine(new Sample()._flag2);
class Sample
{
// The following line causes the error CS0133
//public const int _flag1 = new
Random().Next();
public readonly int _flag2 = new
Random().Next(500);
}
Here is the possible output from this program (it may differ in your
system because I’ve generated the random integers):
447
199
Bonus Q&A
9.T16 I often see people using the terms boxing / unboxing and type
casting interchangeably. Is there any difference?
Answer:
Yes, there is a difference; it may appear confusing, but if you focus on
the primary rule, you can easily avoid the confusion. Basically, using these
terms, you are telling the outside world that you are trying to convert one
type into another, and that is the similarity.
Now let’s focus on the specialties of boxing and unboxing . Boxing and
unboxing are transformations between value types and object types. With
boxing, a copy of the value type from the stack is moved to a heap, and
unboxing does the reverse operation. So, you can say that by using boxing
you cast a value type to a reference type (and obviously, unboxing is the
counterpart of this kind of operation).
But in a broader sense, the word casting does not force you to assume
the movements of elements between stacks and heaps . For example, when
you write something like this:
int i = 1;
double d = i;
there is no problem, and this is not boxing for sure.
9.T17 I often hear that unboxing is risky. Can you explain the
reason behind this statement?
Answer:
This is true not only for unboxing, but any type of downcasting is risky.
This is because you may encounter exceptions when you run that code. For
example, attempting to unbox a null object causes
NullReferenceException. Also, if the target object is incompatible,
you encounter InvalidCastException. So, you need to be careful
before you downcast or unbox an object.
Programming Skills
By using the following questions, you can test your programming skills in
C#. Note that if I do not mention the namespace, you can assume that all
parts of the program belong to the same namespace.
Static Data
9.P1 Can you compile the following code?
Explanation:
The GetArea method is static. So, instead of creating an instance of
the Rectangle class, you can access this method through the class name
directly. Remember that when a method is marked with the static
keyword, it belongs to the class as a whole, instead of an instance.
Microsoft says the following (see
https://docs.microsoft.com/en-
us/dotnet/csharp/language-
reference/keywords/static ):
Answer:
No. You’ll receive the following compile-time error:
Additional note:
The following code segment works fine:
class Rectangle
{
readonly int _flag = 20;
Answer:
No. You’ll receive the following compile-time error:
CS0120 An object reference is required for the
non-static field, method, or property
'Rectangle._flag'
Additional Note:
The error message is self-explanatory. You can use any of the following
options to remove this compile-time error:
Make the field static; for example, you can use static readonly
int _flag = 20;.
Make the DisplayFlag method nonstatic by removing the static
modifier.
using Extensions;
namespace Extensions
{
public static class IntExtension
{
public static int IncrementByFive(this int
i)
{
return i + 5;
}
}
}
Answer:
This code can compile and run successfully. You’ll get the following
output:
flag2 is 20
Explanation:
You have seen an example of an extension method. It is a static method,
but you can call it with the instance method syntax. So, the following code
segment does not cause any errors:
You can see that the int parameter is preceded by the this modifier.
This means that you are working on the int type.
POINT TO NOTE
You may have noticed that I have used the using directive. Why? An
extension method makes it seem like it is defined in the original type, but
actually, that is not the case. It is in scope only when you explicitly
import the required namespace. So, if you comment out (or exclude) the
following line:
using Extensions;
you’ll see a compile-time error that says the following:
CS1061 'int' does not contain a definition for 'IncrementByFive' and
no accessible extension method 'IncrementByFive' accepting a first
argument of type 'int' could be found (are you missing a using directive
or an assembly reference?)
using Extensions;
Sample sample = new();
sample.DisplayNumber();
sample.DisplayString();
public class Sample
{
int _flag = 10;
public void DisplayNumber()
{
Console.WriteLine(_flag);
}
}
namespace Extensions
{
public static class SampleExtension
{
public static void DisplayString(this
Sample s)
{
Console.WriteLine(s.ToString());
}
}
}
Answer:
This code can compile and run successfully. You’ll get the following
output:
10
Sample
Additional Note:
This time you have seen an example where an extension method works
on a custom type (i.e., Sample).
9.P7 Suppose, in the previous program (9.P6) , you added one more
line of code to the extension method as follows:
Explanation:
An extension method cannot access the private variables in the type it is
extending. This is why by using the extension methods, you do not violate
the encapsulation principle.
9.P8 Predict the output of the following code segment:
using Extensions;
Sample sample = new();
sample.Display();
namespace Extensions
{
public static class SampleExtension
{
public static void Display(this Sample s)
{
Console.WriteLine("The extension
method is called.");
}
}
}
Answer:
This code can compile and run successfully. You’ll get the following
output:
Explanation:
Using an extension method, you cannot override the original definition.
So, if you have an extension method with the same name and signature as
an interface or class method, it’ll never be called. Microsoft makes this
clear by saying the following ( https://docs.microsoft.com/en-
us/dotnet/csharp/programming-guide/classes-and-
structs/extension-methods ):
Answer:
This code can compile and run successfully. You’ll get the following
output:
Explanation:
A value type variable directly contains its data, and a reference type
variable contains a reference to its data.
So passing a value type variable to a method by value means that you
pass a copy to the method. So, if the method makes any change to that
copied parameter, it does not affect the original data.
Note If you want the change made by the called method to reflect
original data, you need to pass it by reference with either a ref keyword
or an out keyword. You’ll see them in the upcoming programs.
Answer:
This code can compile and run successfully. You’ll get the following
output:
Inside Change(), the value is 100
The final value is 100
Explanation:
This time the changed value is reflected outside the Change() method
too. Notice that we have used the ref keyword, which has done the trick.
Using ref int x, we are not targeting the value of the integer parameter;
rather, it is a reference to the integer (which is the flag variable in this
case).
POINTS TO NOTE
You need to initialize the flag variable before you pass it to the
Change() method; otherwise, you’ll see the following compile-time
error:
CS0165 Use of unassigned local variable 'flag'
int flag;
Sample.Change(out flag);
Console.WriteLine($"The final value is {flag}");
class Sample
{
internal static void Change(out int x)
{
x = 100;
Answer:
This code can compile and run successfully. You’ll get the following
output:
To use an out parameter, both the method definition and the calling
method must explicitly use the out keyword.
So, if you comment out the x=100; in 9.P11, you’ll see the following
errors:
POINTS TO REMEMBER
For the out parameter , this initialization is not mandatory (but for ref,
it is a must). On the other hand, you need to assign a value before it
comes out of the function.
Explanation:
The is keyword checks if the runtime type of an expression is
compatible with a given type. It compares with a given type and returns true
if the casting is possible; otherwise, it will return false.
9.P13 Can you compile the following code?
double i = 60.5;
object iBoxed = i;
double? jNullable = 40.2;
if (iBoxed is double a && jNullable is double b)
{
Console.WriteLine($"Result is {a + b}");
}
Answer:
This code can compile and run successfully. You’ll get the following
output:
Result is 100.7
Explanation:
This example shows the use of a declaration pattern. It is useful to
check whether the runtime type of an expression is compatible with a given
type. You can see that using this pattern, you can declare a new local
variable. When a declaration pattern matches an expression, you can assign
the expression result to the declared variable. This program demonstrates
this usage.
Using as Operator
9.P14 Can you compile the following code?
Answer:
This code can compile and run successfully. You’ll get the following
output:
Explanation:
You often see the use of the as keyword in the expression E as T
where E is an expression that returns a value and T is the name of a type or
a type parameter. Using the as keyword, you can cast a given object to a
specified type if it is convertible; otherwise, it will return null. So, using
this keyword, you can check the “conversion possibility” as well as the
“actual conversion.” The as keyword is very helpful because it never
throws an exception.
This program shows a common pattern: you compare the result of an as
expression with null to check whether the conversion is successful.
You may note an important point here: starting with C# 7.0, you have
got additional flexibility with the is keyword . In 9.P13, you saw that you
can use the is operator both to test whether the conversion succeeds and
then to assign its result to a new variable.
9.P15 Can you compile the following code?
Answer:
No. You’ll receive the following compile-time error:
Explanation:
The error message is self-explanatory. In addition, I want you to note
another important point: the Microsoft documentation says the following
(see https://docs.microsoft.com/en-
us/dotnet/csharp/language-
reference/operators/type-testing-and-cast#as-
operator ):
The as operator considers only reference, nullable, boxing, and
unboxing conversions. You can't use the as operator to perform a
user-defined conversion. To do that, use a cast expression.
Additional Note:
You have seen a technique that helps you set multiple values using a
single method.
new Sample().Display();
class Sample
{
private static readonly int s_flag = 1;
static Sample()
{
s_flag = 25;
}
public void Display()
{
Console.WriteLine($"Flag is {s_flag}");
}
}
Answer:
This code can compile and run successfully. You’ll get the following
output:
Flag is 25
Explanation:
You are allowed to modify the readonly field inside the constructor.
Refer to the online documentation (
https://docs.microsoft.com/en-
us/dotnet/csharp/language-
reference/keywords/readonly ) that states the following:
new Sample().Display();
class Sample
{
private static readonly int s_flag = 1;
public Sample()
{
s_flag = 25;
}
public void Display()
{
Console.WriteLine($"Flag is {s_flag}");
}
}
Answer:
No. Notice that this time the constructor is not static. You’ll receive the
following compile-time error :
Explanation:
Microsoft clearly states the following (
https://docs.microsoft.com/en-
us/dotnet/csharp/language-
reference/keywords/readonly ):
Console.WriteLine(new Sample()._flag);
class Sample
{
internal const int _flag = 1;
Answer:
No. You’ll receive the following compile-time error:
CS0176 Member 'Sample._flag' cannot be accessed
with an instance reference; qualify it with a type
name instead
Explanation:
The _flag variable is implicitly static. So, you cannot access it
through an instance reference. Microsoft’s online documentation (
https://docs.microsoft.com/en-
us/dotnet/csharp/programming-guide/classes-and-
structs/constants ) says the following about it:
Additional Note:
Note that the following code:
Explanation:
I told you (see 9.T13) that you can assign to a readonly field multiple
times. But you must initialize them in the field declaration or inside a
constructor. You cannot assign a new value to the field once the control
comes out from the constructor.
One C# 10 Feature
9.P22 Can you compile the following code?
Console.WriteLine(Description);
Answer:
Starting with C# 10, this code can compile and run successfully. You’ll
get the following output:
C# 10.0 supports constant interpolated strings.
Additional Note:
If you try to run this code in C#9, there are two types of errors:
OceanofPDF.com
Part III
Advanced C#
Advanced C#
Years ago, I attended a course on advanced C#. The instructor started the
discussion with delegates. A few years later, I attended a seminar, and it
began in the same way. So, we know that delegates are the foundation for
advanced C#. But the discussion of delegates is incomplete without events
and lambda expressions. These are the integral parts of advanced
programming in C#. Part III of this book covers all these topics.
In addition, you’ll see big chapters on generics (Chapter 13) and
multithreading (Chapter 14). These help you write better and more efficient
C# programs. Finally, in Chapter 15), you will learn about some preview
features. Reading these chapters will make you confident in C#
programming.
I have excluded other advanced topics such as asynchronous
programming and LINQ queries from this book to keep the target readers in
my mind. Still, Part III is essential to understand advanced features in C#.
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
V. Sarcar, Test Your Skills in C# Programming
https://doi.org/10.1007/978-1-4842-8655-5_10
10. Delegates
Vaskaran Sarcar1
Welcome to Part III of the book. Here you will start reviewing the concept of
delegates, which are the foundation for advanced programming in C#. Before we
jump into it, let’s review some basic stuff.
To create an object, say obA from class A, you can write something like the
following: A obA=new A();. Here, obA is a reference (often called a variable)
that points to an object named A. Similarly, a delegate is also a reference type, but it
points to methods.
Let’s understand its significance from another point of view. You know what a
variable is and how it behaves. You have seen that you can put different Boolean
values (true/false), strings (or, words), and numbers (integer, double, etc.) in
the respective type of variables. But when you use delegates, you can assign a method
to a variable and pass it around.
In short, using delegates, you can treat your methods like objects. So, you can
store a delegate in a variable, pass it as a method parameter, and return it from a
method. This chapter focuses on delegates and covers the following topics:
Delegates and their uses
Multicast delegates
Some commonly used built-in delegates
Covariance and contravariance using delegates
Let us review and test your understanding of these topics now.
Theoretical Concepts
This section includes the theoretical questions and answers on delegates in C#.
Delegates in C#
10.T1 Explain delegates in C# with an example.
Answer:
A delegate is a reference type derived from System.Delegate, and its
instances are used to call methods with matching signatures. The dictionary meaning
of the word delegate is “a representative.” Therefore, you can say that these delegates
represent methods with matching signatures.
POINTS TO NOTE
Delegates are one of the most important concepts in C# programming. I want you
to understand them clearly. So, I begin this chapter with a simple demonstration
that indicates the core purpose of using a delegate. Before proceeding, keep the
following points in your mind:
If you analyze the IL code, you’ll find that once you create a delegate, the C#
compiler turns it into a class that is derived from Delegate. More specifically,
it derives from MulticastDelegate, which is derived from Delegate.
But the delegate types are sealed. So, your custom classes cannot derive from
Delegate.
The online documentation at https://docs.microsoft.com/en-
us/dotnet/csharp/fundamentals/coding-style/coding-
conventions suggests you use Func<> and Action<> instead of defining
delegate types. You’ll experiment with these built-in delegates soon.
Ideally you should put the delegates and classes inside the namespaces. If you
do not follow this convention, you’ll see a message from Visual Studio IDE
saying: CA1050 Declare types in namespaces. What does this
mean? It suggests you define types within namespaces to organize your code
better. This activity also helps you avoid name collisions. When you declare a
type without a namespace, it belongs to the global namespace that cannot be
referenced in code.
Initially, I’ll use full syntax for using a delegate. Shortly, you’ll see the concise
syntax for it.
// Case-1
Console.WriteLine("Called the Sum method without using a
delegate.");
Console.WriteLine($" a+b={Sum(a, b)}");
// Case-2
Program_10_T1.Calculate del = new
Program_10_T1.Calculate(Sum);
// Program_10_T1.Calculate del = new (Sum);// OK
// Also OK. I'll use the following next time onwards.
// Program_10_T1.Calculate del = Sum;
namespace Program_10_T1
{
public delegate int Calculate(int x, int y);
}
Once you compile and run this program, you’ll see the following output:
You have seen me use the full syntax of a delegate. You can also write an
equivalent line of code using the simplified new expression syntax. In addition, you
can use a condensed syntax that I’ll show you next time. Let’s look at the equivalent
lines of code with supporting comments for your easy reference (excluding the
namespace name):
Calculate del = new Calculate(Sum); // Using full syntax
Calculate del = new (Sum);// Using simplified new
expression
Calculate del = Sum; // Condensed Syntax. I'll use this
next time.
POINTS TO NOTE
This program shows that del(a,b) is the syntactic shortcut for
del.Invoke(a,b).
Program_10_T2.MultiDel del =
Program_10_T2.Sample.DisplaySum;
del += Program_10_T2.Sample.DisplayDifference;
del += Program_10_T2.Sample.DisplayProduct;
del(90, 10);
namespace Program_10_T2
{
public delegate void MultiDel(int x, int y);
class Sample
{
public static void DisplaySum(int a, int b) =>
Console.WriteLine($"Sum={a + b}");
Sum=100
Difference=80
Product=900
POINTS TO NOTE
You can see that I have added methods to the delegate using the += operator. The
Visual Studio IDE also suggests that you use this combined assignment. But, you
can surely guess that instead of using the combined assignment, you could write
something like the following:
del = del+ Program_10_T2.Sample.DisplayDifference;
You can remove a method from this delegate using the -= operator as follows:
del -= Program_10_T2.Sample.DisplayDifference;
Additional Note:
It is useful to know that when you use the +, += , - , or -= operators in similar
cases for a delegate, you actually use the built-in static methods Combine and
Remove from the Delegate class. To test this, you can play with the following
code segment:
Console.WriteLine("\nInvoking del2.");
del2(90, 10);
Invoking del2.
Sum=100
Difference=80
10.T3 In the preceding example, the return type of the multicast delegate is
void. What is the intention behind this?
Answer:
Normally, you use a multicast delegate to invoke multiple methods together. In C#
programming, if you experiment with methods that have a nonvoid return type, you
will receive the return value from the last method only. All of the other results are
completely ignored. You’ll see such a program (10.P7) in the “Programming Skills”
section. This is the reason you rarely want to use multicast delegates to invoke
methods with nonvoid return types.
10.T4 Does this mean that if you use a nonvoid return type with a multicast
delegate, you will not see any compilation error. Is this understanding correct?
Answer:
Yes. As mentioned, in this case, you’ll receive the return value from the last
method only. If it makes sense to you, you need to write your code accordingly.
10.T5 Can we use delegates to define callback methods?
Answer:
Yes. It is one of the key purposes of using delegates.
10.T6 What is the invocation list of a delegate?
Answer:
A delegate instance encapsulates and maintains a set of methods. This is called the
invocation list , which may consist of one or more elements. When you call a
multicast delegate , the delegates in the invocation list are called synchronously in the
order in which they appear. If any error occurs during the execution, it throws an
exception.
10.T7 This means if an exception is raised due to a multicast delegate
invocation, it is a challenging situation to handle. Is this correct?
Answer:
Absolutely. This is why you need to pay special attention to guarding those
situations .
Author’s Note: In my book Getting Started with Advanced C#, I demonstrated
this case with a simple program. Here I leave this exercise to you.
Generic Delegates
Let’s review some of the generic delegates . In this section, I’ll cover three important
built-in generic delegates called Func, Action, and Predicate, which are very
common in generic programming. Let’s start.
10.T8 Can you name some of the generic delegates in C#? Why are they
useful?
Answer:
At the beginning of this chapter, I showed you that Microsoft wants you to use
Func<> and Action<> instead of defining delegate types. These are built-in
generic delegates. This gives you the clue that though you can create custom
delegates, you can avoid this activity because you have built-in support for generic
delegates that are very flexible and cover most situations.
10.T9 How does a Func delegate differ from an Action delegate?
Answer:
The Action delegates have a void return type, but the Func delegates have a
generic return type.
At the time of this writing, there are 17 overloaded versions of the Func delegate
. They can take 0 to 16 input parameters but always have one return type. Here’s an
example:
Func<out TResult>
Func<in T, out TResult>
Func<in T1, in T2,out TResult>
Func<in T1, in T2, in T3, out TResult>
......
class Sample
{
public static int ApproximateTotal(double a, double b)
{
// Some other code, if any
double temp = a + b;
return (int)temp;
}
}
So, in this case, the Func delegate considers all the input arguments (both are
double in this example) and returns an int. Now you may be confused and ask
which parameter denotes the return type. If you move your cursor over this in the
Visual Studio IDE, you can see that the last parameter (TResult) is considered the
return type of the function, and the others are considered input types. For example,
here are the definitions from Visual Studio:
//
// Summary:
// Encapsulates a method that has two parameters and
returns a value of
// the type specified by the TResult parameter.
//
// Parameters:
// arg1:
// The first parameter of the method that this
delegate encapsulates.
//
// arg2:
// The second parameter of the method that this
delegate encapsulates.
//
// Type parameters:
// T1:
// The type of the first parameter of the method that
this delegate encapsulates.
//
// T2:
// The type of the second parameter of the method
that this delegate encapsulates.
//
// TResult:
// The type of the return value of the method that
this delegate encapsulates.
//
// Returns:
// The return value of the method that this delegate
encapsulates.
public delegate TResult Func<in T1, in T2, out TResult>
(T1 arg1, T2 arg2);
Action delegate s can take 1 to 16 input parameters but do not have a return
type. The overloaded versions are as follows:
Action<in T>
Action<in T1,in T2>
Action<in T1,in T2, in T3>
....
Action<in T1, in T2, in T3,in T4, in T5, in T6,in T7,in
T8,in T9,in T10,in T11,in T12,in T13,in T14,in T15,in
T16>
For example, if you have a method called DisplaySum inside the Sample
class, this method takes two ints as input parameters whose return types are void, as
follows:
class Sample
{
public static void DisplaySum(int a, int b)
{
Console.WriteLine($"The sum of {a} and {b} is:
{a+b}");
// Some other code, if any
}
}
Now you can use an Action delegate to get the sum of two integers, as follows:
10.T10 Can you use a Func delegate to point to a method that has a void
return type?
Answer:
If you have a method with a void return type, it is recommended that you use an
Action delegate to point it. Let’s consider the previous code segment:
class Sample
{
public static void DisplaySum(int a, int b)
{
Console.WriteLine($"The sum of {a} and {b} is:
{a+b}");
// Some other code, if any
}
10.T11 I have learned that in the case of method overloading, the return type
of the methods doesn’t matter, but in the context of delegates, it looks like it
matters. Is this understanding correct?
Answer:
Yes. It’s an important point to remember. Microsoft (
https://docs.microsoft.com/en-
us/dotnet/csharp/programming-guide/delegates/ ) says the
following about it:
You’ll experiment with these concepts in the “Programming Skills” section soon.
10.T14 What is a Predicate delegate?
Answer:
A Predicate delegate is used to evaluate something. For example, let’s assume
that you have a method that defines some criteria and you need to check whether an
object can meet the criteria.
Suppose inside the Sample class you have a method to evaluate whether an input
is greater than 50:
class Sample
{
public static bool TestGreaterThan50(int input)
{
return input > 50;
}
}
You can use the Predicate delegate to perform the test, as follows:
Programming Skills
By using the following questions, you can test your programming skills in C#. Note
that if I do not mention the namespace, you can assume that all parts of the program
belong to the same namespace.
Custom Delegates
10.P1 Predict the output of the following code segment :
namespace CustomDelegates
{
public delegate int MyDel1(int x);
public delegate int MyDel2(int x, int y);
}
class Sample
{
public static int AddFive(int a) { return a + 5; }
public int AddFive(int a, int b) { return a + b + 5;
}
}
Answer:
This code can compile and run successfully. You’ll get the following output:
25+5= 30
25+35+5= 65
Additional Note:
I have shown you this program to demonstrate these two important points:
The C# compiler can bind the correct overloaded method.
Notice that AddFive(int a){...} is a static method and AddFive(int
a, int b){...} is an instance method. So, using delegates, you can point to a
static method as well as a nonstatic method.
10.P2 Can you compile the following code?
namespace CustomDelegate
{
public delegate int MyDel(int x, int y);
}
class Sample
{
public static int Sum(int a, int b)
{
Console.WriteLine("The Sum method of the Sample
class
is invoked.");
return a + b;
}
}
Answer:
No. You’ll receive the following compile-time error:
CS7036 There is no argument given that corresponds to
the required formal parameter 'y' of 'MyDel'
Explanation:
The error is easy to understand: to invoke the Sum method, you need to pass the
second argument too. For example, the following code can invoke the intended
method properly:
So, you understand that when you want to pass any method to a delegate, the
delegate signature and the method signature need to match. For this reason, they are
often called type-safe function pointers .
10.P3 Predict the output of the following code segment:
Answer:
No. You’ll receive the following compile-time error:
Explanation:
Microsoft’s online documentation at https://docs.microsoft.com/en-
us/dotnet/csharp/misc/cs0644?
f1url=%3FappId%3Droslyn%26k%3Dk(CS0644) says the following:
Classes cannot explicitly inherit from any of the following base classes:
System.Enum, System.ValueType, System.Delegate, and System.Array. These
are used as implicit base classes by the compiler .
Multicast Delegates
10.P4 Can you compile the following code?
Program_10_P4.MultiDel multiDel =
Program_10_P4.Sample.Show1;
multiDel += Program_10_P4.Sample.Show2;
multiDel += Program_10_P4.Sample.Show3;
multiDel();
namespace Program_10_P4
{
public delegate void MultiDel();
class Sample
{
public static void Show1()
{
Console.WriteLine("Show1() is called.");
}
public static void Show2()
{
Console.WriteLine("Show2() is called.");
}
public static void Show3()
{
Console.WriteLine("Show3() is called.");
}
}
}
Answer:
Yes, this code can compile and run successfully. You’ll get the following output:
Show1() is called.
Show2() is called.
Show3() is called.
Explanation:
You have seen an example of a multicast delegate. When a delegate is used to
encapsulate more than one method of a matching signature, you call it a multicast
delegate.
Author’s Note: You may remember that System.MulticastDelegate
inherits from System.Delegate.
10.P5 Predict the output of the following code segment:
class Sample
{
public static void Show1()
{
Console.WriteLine("Show1() is called.");
}
public static void Show2()
{
Console.WriteLine("Show2() is called.");
}
public static void Show3()
{
Console.WriteLine("Show3() is called.");
}
}
}
Answer:
The program segments of 10.P4 and 10.P5 are the same (the name of the
namespace is changed only to match the question number); the only difference is that
you have used the built-in Action delegate instead of the custom delegate that was
used in 10.P4. As a result, you will see the same output as in 10.P4.
Show1() is called.
Show2() is called.
Show3() is called.
Generic Delegates
10.P6 Can you compile the following code?
Explanation:
This time you have seen an example of a built-in Func delegate . Notice that the
Sum method accepts two double types as the arguments and returns the result as an
int. So, I have used Func<double, double, int> (the last type indicates the
return type). I know what you are thinking: yes, this is not great code. I intentionally
typecast the double to an int to match the return type. It is because I wanted to use a
different return type to help you not to be confused with other parameters in the
Func delegate.
10.P7 Can you compile the following code?
namespace Program_10_P7
{
class Sample
{
public static int Sum(int a, int b)
{
return a + b;
}
public static int Difference(int a, int b)
{
return a - b;
}
}
}
Answer:
This code can compile and run successfully. You’ll get the following output:
The final value is 5
Explanation:
You have seen that to compile and run a program that uses a multicast delegate,
you don’t need to use the methods that have the void return type only. But the
problem is that if you code like this, then you get the value from the last invoked
method in the invocation/calling chain. All other return values are discarded in
between, but there will be no alert for you. Therefore, it is suggested that you
experiment with a multicast delegate for the methods that have void return types .
Variance in Delegates
10.P8 Can you compile the following code?
namespace Prog_10_P8
{
class Vehicle
{
Vehicle? vehicle;
public Vehicle CreateVehicle()
{
vehicle = new Vehicle();
Console.WriteLine("One vehicle is created.");
return vehicle;
}
}
class Bus : Vehicle
{
Bus? bus;
public Bus CreateBus()
{
bus = new Bus();
Console.WriteLine("One bus is created.");
return bus;
}
}
}
Answer:
Yes, this code can compile and run successfully. You’ll get the following output:
Explanation:
The first line of the output is easy to understand. There is no magic. But the
second line of output is interesting. So, look at this line of code again:
del = busOb.CreateBus;
You can see that the compiler did not complain about this line because of the
covariance support for delegates.
10.P9 Can you compile the following code?
namespace Prog_10_P9
{
class Vehicle
{
public static void ShowVehicle(Vehicle myVehicle)
{
Console.WriteLine("ShowVehicle is called.");
Console.WriteLine("This is a generic
vehicle.\n");
}
}
class Bus : Vehicle
{
public static void ShowBus(Bus myBus)
{
Console.WriteLine("ShowBus is called.");
Console.WriteLine("This is a bus.\n");
}
}
}
Answer:
Yes, this code can compile and run successfully . You’ll get the following output:
ShowBus is called.
This is a bus.
ShowVehicle is called.
This is a generic vehicle.
Explanation:
The first two lines of the output are easy to understand. There is no magic. But the
next two lines are interesting. So, notice the line of code again:
del = Prog_10_P9.Vehicle.ShowVehicle;
The ShowVehicle method accepts a Vehicle object, but not a Bus object as
a parameter; still, the delegate can point to it. The compiler did not complain about
this line due to the contravariance support for delegates .
Delegate Compatibility
10.P10 Can you compile the following code?
namespace Prog_10_P10
{
public delegate int MyDel1(int x, int y);
public delegate int MyDel2(int x, int y);
class Sample
{
public static int Sum(int a, int b)
{
Console.WriteLine("The Sum method is
invoked.");
return a + b;
}
}
}
Answer:
No. You’ll receive the following compile-time error:
Explanation:
The delegate types are all incompatible with one another, even if their signatures
are the same. It is interesting to note that if you write the following:
// The following is OK
Prog_10_P10.MyDel2 del2 = new Prog_10_P10.MyDel2(del1);
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
V. Sarcar, Test Your Skills in C# Programming
https://doi.org/10.1007/978-1-4842-8655-5_11
11. Events
Vaskaran Sarcar1
The support for events is considered one of the most exciting features in C#. It is useful
because you can send notifications from one code segment to another code segment by
using events. These are common in GUI applications such as when you fill in an online
form or click the submit button and then notice some interesting change in the UI (for
example, you may see a pop-up message saying that the data was submitted properly). The
Visual Studio IDE provides a lot of support when your code deals with events. Since these
concepts are at the core of C#, it’s good to learn them well. So, before we jump into the
Q&A sessions, I suggest you read the following points:
Delegates are the backbones of events. See Microsoft’s online documentation at
https://docs.microsoft.com/en-us/dotnet/csharp/programming-
guide/events/how-to-implement-custom-event-accessors where it
says: “An event is a special kind of multicast delegate that can only be invoked from
within the class that it is declared in.” So, it will be helpful for you to learn about
delegates before you play with events.
In an event-driven program, you notice a publisher-subscriber model where one object
raises a notification (event) and one or multiple objects listen to those events. The object
that raises the event is called the sender (or publisher or broadcaster), and the object that
receives the event is called the receiver (or, a subscriber).
A sender does not care about how the receivers interpret the events. The sender also does
not care about who is registering or unregistering to get their events or notifications.
You can relate this model to Facebook or Twitter. If you follow someone, you can get
notifications when that person updates something in their profile. If you do not want to
get notifications, you can always unsubscribe. In short, a subscriber can decide when to
start listening to events and when to stop listening to events. (In programming terms, you
can specify when to register for events and when to unregister the events.)
The publisher is the type that contains the delegate. Subscribers register themselves by
using += on the publisher’s delegate and unregister themselves by using -= on that
delegate. So, when we apply += or -= to an event, it has a special meaning (in other
words, in those contexts, these are not shortcuts for assignments).
Subscribers do not communicate with each other. As a result, you can make a loosely
coupled system. It is often the key goal in an event-driven architecture.
You will often see the use of a built-in EventHandler delegate (or its generic
version). It is a predefined delegate made for simple events. This delegate has a return
type of void and has two parameters, a nullable object, which is the “sender” of the
event, and an EventArgs object. The EventArgs object stores some basic
information about the event itself. I am showing its definition from Visual Studio for
your immediate reference:
public delegate void EventHandler(object? sender,
EventArgs e);
It is also interesting to know that to support backward compatibility, many events in the
.NET Framework follow the nongeneric custom delegate pattern.
Here is an example of an event declaration:
public event EventHandler FlagChanged;
This simply indicates that FlagChanged is the name of the event and
EventHandler is the corresponding delegate.
You can note that the modifier need not be public always. You may choose other
nonpublic modifiers such as private, protected, and internal for your event.
You can also use keywords such as static, virtual, override, abstract,
sealed, and new in this context.
This chapter helps you to review your understanding of events and discusses the
following:
Events creation and their uses
How to pass data when an event is raised
Use of event accessors
Use of interface events (both implicit and explicit)
Simplified coding with events
Let us test your understanding of these topics now.
Theoretical Concepts
This section includes the theoretical questions and answers on C# events.
Understanding Events
11.T1 Explain a simple program to demonstrate the use of an event.
Answer:
Before you see the complete program, I want you to review the following discussions
and code explanations .
Before you declare the event, you need a delegate. But you won’t see the delegate
declaration in this program. This is because I have used the predefined delegate
EventHandler. Here is the code for the event declaration:
sender.Flag = 1;
The Receiver class plays the role of the consumer. It has a method called
GetNotification. To get a notification from the sender, you’ll notice the use of the
following code with supporting comments:
After some time, the receiver does not show any interest to get further notifications
from the sender. So, it unsubscribes the event using the following code:
// Unregistering now
sender.FlagChanged -= receiver.GetNotification;
Now the question is: how do you raise this event? In legacy code, you might see the
following format to raise an event:
if (FlagChanged != null)
{
FlagChanged(this, EventArgs.Empty);
}
Let us analyze this code. It checks whether the event is null. If so, there is no event
handler attached to this event. This check is important because raising an event with no
event handlers results in a NullReferenceException (you can check this by
commenting out if (FlagChanged != null) in the upcoming program).
Once you know an event has event handlers attached to it, you can raise it by calling
this event with the parameters required by the delegate. For example, in this case, you need
to pass a reference to the sender (this) and an EventArgs object (though you have used
the static EventArgs.Empty object in this case).
Here is the complete program (use the comments for your reference):
Console.WriteLine("---10.T1---");
Sender sender = new();
Receiver receiver = new();
// Receiver registers for a notification from sender
sender.FlagChanged += receiver.GetNotification;
// Simplified form:
// FlagChanged?.Invoke(this, EventArgs.Empty);
}
}
class Receiver
{
public void GetNotification(object? sender,
System.EventArgs e)
{
Console.WriteLine($"ALERT: The flag value is changed
in the Sender.");
}
}
Once you compile and run this program, you’ll see the following output:
POINT TO NOTE
Starting now, to avoid Visual Studio’s message of IDE1005 Delegate
invocation can be simplified, you’ll see a simplified version of this code
segment, as shown here :
// Simplified form.
FlagChanged?.Invoke(this, EventArgs.Empty);
These event accessors are similar to property accessors except they are named add
and remove . In normal cases, you do not need to supply custom event accessors.
But when you define them, you are instructing the C# compiler not to generate the
default field and accessors for you.
When you look closely at these accessors, you’ll understand that by using these
constructs you create a property-like wrapper around your delegate. As a result, you have
better control over your code. These are also useful when you explicitly implement an
interface that contains an event. The program that is discussed in 11.P5 shows you an
example of this.
11.T4 How can my class implement multiple interfaces when the interface events
have the same name?
Answer:
Yes, this situation is interesting, and you can follow the explicit interface
implementation technique. But there is one important restriction for you. It says that you
need to supply the add and remove event accessors in such a case. Normally, the
compiler supplies these accessors for you, but, in this case, it cannot.
11.T5 I understand that delegates are the backbone for events, and in general, you
follow an Observer design pattern when you write code for events and register and
unregister those events. Is this understanding correct?
Answer:
Design patterns are advanced concepts, and you can refer to the book Design Patterns
in C# (2nd edition) to learn more about them. But if you know them already, the answer to
this question is yes.
11.T6 In some event-driven programs, I see the new keyword. What is this for?
Answer:
The first thing to understand is that throughout this chapter, I have used the short form
for registering an event. For example, in 11.T1, you saw the following line of code when I
registered the event:
sender.FlagChanged += receiver.GetNotification;
Now if you recall the short form used in the context of delegates from Chapter 10, you
can understand that you can write equivalent code as follows:
sender.FlagChanged += new
EventHandler(receiver.GetNotification);
Consider another case in which your sender class contains a sealed event. In this case,
you cannot modify the event in a derived class. Instead, the derived class can use the new
keyword to indicate that it is not overriding the base class event.
11.T7 I understand that EventHandler is a predefined delegate. But in many
places, I’ve seen people use the term event handler in a broad sense. Is there any
special meaning associated with it?
Answer:
Simply, an event handler is a procedure, and you decide what to do when a specific
event is raised (for example, when the user clicks a button in a GUI application). It is
important to note that your event can have multiple handlers, and at the same time, the
method that handles the event can also change dynamically. In this chapter, you’ll see
program segments that give you the idea; they show how the events work and, particularly,
how a receiver class handles them. But if you use a readymade construct like Windows
Forms Designer in Visual Studio, you can code events easily .
Programming Skills
Using the following questions, you can test your programming skills in C#. Note that if I do
not mention the namespace, you can assume that all parts of the program belong to the
same namespace.
class Sender
{
private int _flag;
public int Flag
{
get
{
return _flag;
}
set
{
_flag = value;
OnFlagChanged();
}
}
Explanation:
This program demonstrates that a sender can send a self-notification too.
class Sender
{
public delegate void FlagChangedEventHandler(object?
sender, FlagEventArgs eventArgs);
public event FlagChangedEventHandler? FlagChanged;
class Receiver
{
public void GetNotification(object? sender, FlagEventArgs
e)
{
Console.WriteLine($"ALERT: The flag value is changed
to {e.CurrentFlag}.");
}
}
Answer:
This code can compile and run successfully. You’ll get the following output:
Explanation:
This program uses a custom delegate and shows you how to pass data to an event. It is
also important to note that often you subclass from the EventArgs class to do this.
Additional Note:
If you’d like to use built-in delegates in your program, you can replace the following
two lines of code :
to get the same output. In this case, the built-in Action delegate makes your
programming easy.
But my favorite approach is to use the generic version of an Event delegate. This
delegate has the following definition:
Why is this useful? It is useful because the use of this generic version can also help me
to get the same output, and this single line of code can replace the two lines of code (shown
in the comments) as follows:
Event Accessors
11.P3 Predict the output of the following code segment :
class FlagEventArgs
{
int _currentFlag;
public int CurrentFlag
{
get { return _currentFlag; }
set { _currentFlag = value; }
}
}
class Sender
{
public delegate void FlagChangedEventHandler(object?
sender, FlagEventArgs eventArgs);
private event FlagChangedEventHandler? InnerFlagChanged;
public event FlagChangedEventHandler? FlagChanged
{
add
{
Console.WriteLine("The entry point of the add
accessor.");
InnerFlagChanged += value;
}
remove
{
InnerFlagChanged -= value;
Console.WriteLine("The exit point of the remove
accessor.");
}
}
class Receiver
{
public void GetNotification(object? sender, FlagEventArgs e)
{
Console.WriteLine($"\nALERT: The flag value is changed to
{e.CurrentFlag}.");
}
}
Answer:
This code can compile and run successfully. You’ll get the following output:
Additional Note:
This program shows you the use of event accessors in a program. I want you to note
some other typical points about this program. First, notice the name of the private event in
the following line:
Visual Studio IDE suggests you use Pascal casing to name the private events too. So, I
have named it accordingly.
Generic programming lovers can use the built-in Action delegate (instead of using a
custom delegate that is shown in this program) to produce the same output. You can refer to
the following code segment as a sample:
POINT TO REMEMBER
The documentation at https://docs.microsoft.com/en-
us/dotnet/csharp/programming-guide/events/how-to-implement-
custom-event-accessors says the following:
Although you can substitute any code inside the accessors, we recommend that
you lock the event before you add or remove a new event handler method.
In general, locking operations are expensive. To make these examples simple, I have
ignored this suggestion in this chapter .
Interface Events
11.P4 Predict the output of the following code segment :
Sender sender = new ();
Receiver receiver = new();
// Receiver registers for a notification from the sender
sender.FlagChanged += receiver.GetNotification;
Console.WriteLine("Setting the flag to 1.");
sender.Flag = 1;
Console.WriteLine("Setting the flag to 2.");
sender.Flag = 2;
// Unregistering now
sender.FlagChanged -= receiver.GetNotification;
interface ISender
{
event EventHandler? FlagChanged;
}
class Sender:ISender
{
private int _flag;
public int Flag
{
get
{
return _flag;
}
set
{
_flag = value;
OnFlagChanged();
}
}
FlagChanged?.Invoke(this, EventArgs.Empty);
}
}
class Receiver
{
public void GetNotification(object? sender,
System.EventArgs e)
{
Console.WriteLine($"ALERT: The flag value is changed
in the Sender.");
}
}
Answer:
This code can compile and run successfully. You’ll get the following output:
Explanation:
Similar to implementing an interface method or property, this program implements an
interface event.
11.P5 Predict the output of the following code segment:
remove
{
FlagChanged -= value;
Console.WriteLine(" The exit point of the remove
accessor.");
}
}
}
class Receiver
{
public void GetNotification(object? sender,
System.EventArgs e)
{
Console.WriteLine($"ALERT: The flag value is changed
in the Sender.");
}
}
Answer:
This code can compile and run successfully. You’ll get the following output:
Explanation:
This time you have implemented the interface explicit ly.
Bonus Questions
11.P6 Can you compile the following code?
class Sender
{
public event EventHandler<FlagEventArgs>? FlagChanged;
class Receiver
{
public void GetNotification(object? sender,FlagEventArgs e)
{
Console.WriteLine($"ALERT: The flag value is changed to
{e.CurrentFlag}.");
}
}
Answer:
Yes, this code can compile and run successfully. You’ll get the following output:
Additional Note:
This program shows you a common pattern for an event-driven program. Notice that
FlagEventArgs derives from System.EventArgs, and I have used the generic
version of the EventHandler delegate to pass the event data.
11.P7 Can you compile the following code?
class FlagEventArgs
{
int _currentFlag;
public int CurrentFlag
{
get { return _currentFlag; }
set { _currentFlag = value; }
}
}
class Sender:ParentSender
{
public override event EventHandler<FlagEventArgs>?
FlagChanged;
// The remaining code skipped
}
class FlagEventArgs:EventArgs
{
int _currentFlag;
public int CurrentFlag
{
get { return _currentFlag; }
set { _currentFlag = value; }
}
}
class ParentSender2
{
public virtual event EventHandler<FlagEventArgs>?
FlagChanged;
}
class ParentSender3:ParentSender2
{
public sealed event EventHandler<FlagEventArgs>?
FlagChanged;
}
Answer:
No. You’ll receive the following compile-time error:
Explanation:
The error is self-explanatory. The following line can remove this compile-time error:
Theoretical Concepts
This section includes the theoretical questions and answers on lambda expressions
in C#.
Understanding Lambdas
12.T1 What are the lambda expressions in C#?
Answer:
In simple words , a lambda expression is an anonymous method that is written
in a different form and is easily readable. But what is an anonymous method, and
why is it useful? As the name suggests, an anonymous method is a method that
does not have a name. In certain situations, they are very helpful. For example,
consider a case when you point to a method using a delegate but the method is
present in a different location of the source file (or, in an extreme case, it is in a
different source file). This segregated code is difficult to understand, debug, and
maintain. In such situations, anonymous methods are helpful because in those
cases, you can define an “inline” method without a name to serve your purpose.
The name lambda came from lambda calculus, which can be used to simulate a
Turing machine. You spell it with the Greek letter lambda (λ), which your
keyboard does not have. So, to denote the lambda operator, you use the symbol
=>. The left side of the operator specifies the input parameters (if any), and the
right side of the operator specifies either an expression or a statement block. The
=> part is right-associative, and its precedence is the same as =. While reading the
code that contains the lambda operator, you replace the lambda operator with
“goes to” or “go to” or “arrow” or “become(s).” For example, you read x=>
x+5; as “x goes to x+5.” Similarly, you read (x,y)=>x+y; as “x and y go to
x+y.”
Author’s Note: The C# compiler can convert a lambda expression to either a
delegate instance or an expression tree. This book is not discussing LINQ in detail,
but you have learned about delegates and saw several examples of them in Chapter
10. So, let us focus on delegate instances here.
12.T2 Can you write a simple program to demonstrate the usage of a lambda
expression?
Answer:
To make things simple , I’ll show you a simple program (see Demonstration 1)
that calculates the sum of two integers (10 and 5) using various approaches as
follows:
In the first approach, you’ll notice the use of a normal method (which you are
familiar with). You can use this method to calculate the sum of the ints.
Then I show you how to use a delegate instance to do this.
The last two segments of code show you the use of an anonymous method and a
lambda expression, respectively.
Each program segment generates the same output, and these options give you a
choice of which approach to choose for a program. For a better understanding,
read the comments in the code.
Demonstration 1
class Sample
{
public delegate int Mydel(int x, int y);
public static int Sum(int x, int y)
{
return x + y;
}
}
Output
You’ll receive the following output when you run this program:
Analysis:
Let’s review the statements used for the anonymous method and lambda
expression. In the case of the anonymous method , I have used the following:
(x, y) => x + y;
In most cases, compilers can identify the input parameters and return type
when it deals with a lambda expression. In programming terms, this is called type
inference . Still, in some special cases, you may need to keep this type of
information to allow the compiler to evaluate the expression properly. But this is a
simple case, and the compiler can understand it properly (in this context, notice the
delegate declaration) even if you do not mention the type of the input parameters.
POINT TO REMEMBER
A lambda expression can accept one or multiple parameters. You can also use a
lambda expression that does not accept any parameters.
In the previous program, the lambda expression used multiple parameters.
You have seen that you can list them in parentheses separated by commas as
follows: (x,y)=> x+y;.
If a lambda expression accepts only one parameter, you can omit the
parentheses. For example, you can use either (x)=> x*x; or x=>x*x;.
Both of them can serve the same purpose.
Finally, () => Console.WriteLine("No parameter."); can be
considered an example of a lambda expression that does not have any
parameter.
class Sample2
{
bool IsPositive1(int x) { return x > 0; }
}
class Sample2
{
bool IsPositive2(int x) => x > 0;
}
Author’s Note: If you use more than three lines in a lambda expression, it may
complicate the understanding, and, in those cases, you can opt for a normal
method instead of a lambda expression. Also, I’ve excluded the discussion of the
expression tree because it is a LINQ-related feature, but a discussion of LINQ is
beyond the scope of this book.
12.T6 What is an expression?
Answer:
As per Microsoft, an expression can be a combination of the operators and
operands. It can be evaluated to a single value, method, object, or namespace. An
expression can include a method call, an operator with the operands, a literal value
(a literal is a constant value that does not have a name), or simply a name of a
variable, type member, method parameter, namespace, or type. Here is a simple
example of an expression statement:
int flag=1;
Here flag is a simple name, and 1 is the literal value. Literals and simple
names are the two simplest types of expression.
Restrictions
12.T7 Can you mention some of the restrictions associated with lambda
expressions?
Answer:
Here I mention two important restrictions from the online documentation at
https://docs.microsoft.com/en-
us/dotnet/csharp/language-reference/operators/lambda-
expressions:
A lambda expression can’t contain a goto, break, or continue statement
if the target of that jump statement is outside the lambda expression block. It’s
also an error to have a jump statement outside the lambda expression block if
the target is inside the block.
A lambda expression can’t directly capture an in, ref, or out parameter from
the enclosing method.
You’ll see the code segments covering these concepts in 12.P12 and 12.P13.
The discussion of attributes is out of the scope of this book. But if you are
familiar with it, Microsoft wants you to note that since the lambda expressions are
invoked through the underlying delegate type but the delegate’s Invoke method
does not check attributes on these expressions, the attributes do not have any effect
on these expressions.
12.T8 Can you overload a lambda operator?
Answer:
At the time of this writing, the answer is no.
Programming Skills
By using the following questions, you can test your programming skills in C#.
Note that if I do not mention the namespace, you can assume that all parts of the
program belong to the same namespace.
Basic Concepts
12.P1 Predict the output of the following code segment :
Additional Notes:
This program shows you that a lambda expression can use zero, one, or more
parameters.
Expression-Bodied Members
12.P2 Can you compile the following code?
Answer:
This code can compile and run successfully. You’ll get the following output:
Console.WriteLine("---12.P3---");
class Test
{
int CalculateSum(int a, int b) =>
{
int sum = a + b;
return sum;
}
}
Answer:
You’ll receive compile-time errors with many error messages. For your
reference, I list a few of them here:
Explanation:
Expression syntax to define nonlambda methods is not applicable for statement
lambdas. You can use it only for the expression lambdas.
12.P4 Predict the output of the following code segment:
class Employee
{
readonly int empId=0;
readonly string company = "XYZ company";
string name = string.Empty;
public Employee(int id) => empId = id;
public string Company => company;
public string Name
{
get => name;
set => name = value;
}
public int Id
{
get => empId;
}
}
Answer:
This code can compile and run successfully. You’ll get the following output:
Explanation:
You have seen the use of the expression-bodied members, but you can apply
this concept to properties, constructors, and finalizers. In this program, you have
seen this concept in a constructor, a read-only property , and a read-write property
. For your immediate reference, go through the following code segment and read
the comments to understand what is happening:
Explanation:
You can use local variables in a lambda expression, but the variable must be in
scope.
In a similar program, you can use either the query syntax or the method call
syntax. Here, I have used the method call syntax with a lambda expression. If you
are familiar with LINQ programming, you know about query syntax . By using it,
you can write an equivalent code to find the numbers that are greater than 30 as
follows:
Sample.DoubleMaker del =
(Tuple<int, double> input) => Tuple.Create(input.Item1
* 2,
input.Item2
* 2);
class Sample
{
internal delegate Tuple<int, double>
DoubleMaker(Tuple<int, double> input);
static internal Tuple<int, double>
MakeDouble(Tuple<int, double> input)
{
return Tuple.Create(input.Item1 * 2, input.Item2
* 2);
}
}
Answer:
This code can compile and run successfully. You’ll get the following output:
Explanation:
From C# 7.0 onward, you have built-in support for tuples. If you do not know
about tuples, let me make things simple for you.
To get the same resultant tuple, you could use the normal method call as
follows:
How does this work? Notice that the tuple has only two components. You can
pass this tuple to the MakeDouble method to get a tuple in which the tuple items
are two times bigger than the original tuple items. This is the reason you see the
following method (I have not used this method when I used the lambda
expression):
You can see that inside the tuple, the first component is an int, and the
second one is a double. I’ve multiplied the input arguments by 2 to get the
double of each component and return the result with another tuple. Since I have
invoked the method from a static context, I made the MakeDouble method static.
Now you know how to use tuples with a method. So, let’s understand how to
use it with a lambda expression. In many applications, you’ll see the use of tuples
with built-in delegates (for example, Func, Action, etc.) and lambda
expressions. In 12.P7, you’ll see the use of Func delegate in this context. In this
example, you have seen the use of a user-defined delegate.
As a first step, I declared the following delegate inside the Sample class:
Now I have the delegate. So, I can use a lambda expression like the following:
Sample.DoubleMaker del =
(Tuple<int, double> input) => Tuple.Create(input.Item1
* 2,
input.Item2
* 2);
If you do not use a named component, by default, the fields of the tuple are
named Item1, Item2, Item3, and so forth. So, to get the intended result,
you can use the following lines of code:
Explanation:
You saw equivalent code in 12.P6. The only difference is that this time you
saw the use of the built-in Func delegat e.
Event Subscription
12.P8 Predict the output of the following code segment :
Explanation:
This program shows that you can do an event subscription using a lambda
expression .
12.P9 Predict the output of the following code segment:
Explanation:
This is confusing output. Why? Notice the last line of the output. You may
wonder why you see the message “The flag is changed” after you unsubscribe
from the event. The reason is that if you use the following lines to subscribe to the
event:
and, then later at some point of time, you unsubscribe it with the following
line:
there is no guarantee that the compiler will unsubscribe this event . This is why
you should follow the approach shown in 12.P8.
POINTS TO NOTE
You probably know that to avoid memory leaks in real-world applications, once
you subscribe to an event, you should unsubscribe once the intended job is
done. You have seen the program segments in 12.P8 (good practice) and 12.P9
(bad practice). Experts recommend you store the anonymous method/lambda
expression in a delegate variable and then add this delegate to the event. As a
result, you can keep a track of it, and if you want, you can unsubscribe from the
event properly. So, a common suggestion is that you should not use anonymous
functions to subscribe to an event when you really want to unsubscribe from the
event at a later point in time.
Static Lambda
12.P10 Can you compile the following code?
Answer:
This code can compile and run successfully in C# 9 and later versions. You’ll
get the following output:
100
So, if you run this code in .NET Core 3.1 (which supports C# 8.0), you’ll see
many errors, and one of them is as follows:
Natural Type
12.P11 Can you compile the following code?
// Code Segment-1
Func<string, double> del = (string s) =>
double.Parse(s);
double flag2 = del("23.4");
Console.WriteLine(flag2);
// Code Segment-2
var flag1 = (string s) => double.Parse(s);
Console.WriteLine(flag1("23.4"));
Answer:
This code can compile and run successfully. Both of these code segments will
produce the same output.
23.4
23.4
Explanation:
Actually, a lambda expression in itself doesn’t have a type. But the informal
“type” of a lambda expression refers to the delegate type or Expression type to
which the lambda expression is converted. Microsoft (
https://docs.microsoft.com/en-
us/dotnet/csharp/language-reference/operators/lambda-
expressions ) says the following:
So, the compiler can infer the Parse method in this example as the
Func<string, double> type in code segment 2.
Bonus Questions
12.P12 Predict the output of the following code segment (unsafe code is
allowed in this program):
class Test
{
internal unsafe void SampleMethod()
{
int a = 10;
int* p = &a;
Console.WriteLine($"a={a}");
Console.WriteLine($"*p={*p}");
}
}
Answer:
This code will compile and produce the following output:
a=10
*p=10
};
del();
xx: Console.WriteLine("Level xx");
Answer:
This code cannot compile. You’ll get the following error:
Answer:
You’ll receive the following compile-time error:
CS0748 Inconsistent lambda parameter usage; parameter
types must be all explicit or all implicit
Explanation:
You can see that the compiler could not infer the type. As per the error
message, here are the possible remedies:
Or,
int a = 99;
Sample.Increment(ref a);
class Sample
{
public delegate int Mydel(int x);
public static void Increment(ref int a)
{
// Using anonymous method
Mydel del = delegate (int x)
{
x = a;
return x + 1;
};
Console.WriteLine(del(a));
}
}
Answer:
This code cannot compile. You’ll get the following error:
If you comment out the line x=a; in the previous code, you can see the output
100.
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
V. Sarcar, Test Your Skills in C# Programming
https://doi.org/10.1007/978-1-4842-8655-5_13
13. Generics
Vaskaran Sarcar1
Generics are an integral part of advanced programming. They are one of the coolest
features of C#. Let’s see what Microsoft says about them (see
https://docs.microsoft.com/en-
us/dotnet/csharp/fundamentals/types/generics ):
Generic classes and methods combine reusability, type safety, and efficiency in a
way that their non-generic counterparts cannot. Generics are most frequently
used with collections and the methods that operate on them. The
System.Collections.Generic namespace contains several generic-based collection
classes. The non-generic collections, such as ArrayList are not recommended and
are maintained for compatibility purposes.
This is one of the reasons you’ll find few real-life applications that do not use
generics at their core. This chapter focuses on generics and covers the following topics:
The motivation behind generics
The fundamentals of generic programs
Use of generic interfaces
Use of generic constraints
Use of covariance and contravariance using generics
Self-referencing generic type
Experimenting with generic method’s overloading and overriding
Analyzing the static data in the context of generics
Let us review and test your understanding of these topics now.
Theoretical Concepts
This section includes the theoretical questions and answers on generics in C#.
Generics streamline dynamically generated code. When you use generics with
dynamically generated code you do not need to generate the type. This increases
the number of scenarios in which you can use lightweight dynamic methods
instead of generating entire assemblies.
13.T2 I know that inheritance also promotes reusability. Then why do I use
generics?
Answer:
Inheritance and generics work in different ways. Using inheritance, you inherit from
a base type, and often you use casting and boxing. Generics scores high in this area: they
provide better type safety and reduce casting and boxing. You also know that in the case
of generics, you provide a template that contains placeholder types so that you do not
commit to a specific type for its instances.
13.T3 You said, “Your program can avoid typical runtime errors that may arise
due to improper casting.” Can you explain?
Answer:
In 13.P2, you’ll see the following code, which raises a compile-time error :
using System.Collections;
then there is no compile-time error . However, the mistake is severe because now
you’ll face a runtime error saying the following:
You also understand that catching a bug earlier is good practice. So, a compile-time
error is always better than a runtime error.
Generic Methods
13.T4 Here is a program segment for you. Is SomeMethod a generic method?
class A
{
T SomeMethod<T>(T arg)
{
// Some code
}
}
Answer:
Yes.
13.T5 Here is a program segment for you. Is SomeMethod a generic method?
class Generic<T>
{
T SomeMethod(T arg)
{
// Some code
}
}
Answer:
As per Microsoft’s description, the answer to this question is no. Why? Refer to
Microsoft’s documentation ( https://docs.microsoft.com/en-
us/dotnet/standard/generics/ ):
interface ISample<T>
{
// Some code
}
class Implementor1<U, T1> : ISample<T1>
{
// Remaining code
}
Restrictions
13.T6 Can you mention some of the restrictions in generics?
Answer:
Here are some important restrictions :
In 13.T7 (and 13.P22), you’ll learn that static data is unique for each of the closed
types but not for different constructed types.
You cannot apply the DllImport attribute to a generic method. This attribute is
often used with the extern modifier that is used to tell that the method is defined
outside the C# code. Here you just mention the method name because the actual code
is placed in a different location. In addition, these methods are static. So, the
following segment of code:
using System.Runtime.InteropServices;
class GenericClassDemo2<T>
{
[DllImport("Somefile.dll")] // Error CS7046
private static extern void SomeMethod();
}
You cannot use a pointer type as a type argument. So, the last line in the following
code segment:
class Sample<T>
{
static unsafe void ShowMe()
{
int a = 10; // ok
int* p; // ok
p = &a; // ok
T* _flag; // error
}
}
will raise the compile-time error saying the following:
CS0208 Cannot take the address of, get the size of, or
declare a pointer to a managed type ('T')
When you apply generic constraints, you have to follow certain rules. You’ll see some
of them in the “Programming Skills” section. For example, in 13.P10, you’ll see that
to remove the compile-time error CS0401, you need to place the new() constraint at
the end of the constraint list.
Bonus Questions
13.T7 How does the static data work in the context of generic programming?
Answer:
Static data is unique for each of the closed types. So, you can see that the generic
types do not share the same static member among them. You can refer to the program
13.P22 in this context.
13.T8 Is there any other way to share data among closed types?
Answer:
One probable solution is to make a nongeneric base class to hold the static fields
(without type parameters). Then you make a generic type that can inherit from it.
13.T9 Can you give examples of built-in generic classes?
Answer:
I often use the List and Dictionary classes instead of ArrayList and
Hashtable classes in my programs. You may know that List and Dictionary are
generic classes, but the other two are non-generic classes.
You’ll see the use of List<int> in 13.P2. So, let me show you the use of
Dictionary. Consider the following code:
Once you compile and run this program, you’ll see the following output:
Programming Skills
By using the following questions, you can test your programming skills in C#. Note that
if I do not mention the namespace, you can assume that all parts of the program belong
to the same namespace.
Basic Concepts
13.P1 Can you compile the following code?
Answer:
Yes, this code can compile and run successfully. You’ll get the following output:
Explanation:
You have seen a simple example of using generics in a program.
13.P2 Can you compile the following code?
Answer:
No. You’ll receive the following compile-time error:
Sample.PrintDefault<int>();
Sample.PrintDefault<int?>();
Sample.PrintDefault<sbyte>();
Sample.PrintDefault<double>();
Sample.PrintDefault<bool>();
Sample.PrintDefault<string>();
Sample.PrintDefault<object>();
Sample.PrintDefault<System.Numerics.Complex>();
Sample.PrintDefault<List<int>>();
Sample.PrintDefault<List<string>>();
class Sample
{
internal static void PrintDefault<T>()
{
T? defaultValue = default;
string? printMe = (defaultValue == null) ? "null" :
defaultValue.ToString();
Console.WriteLine($"The default value of {typeof(T)}
is {printMe}.");
}
}
Answer:
This code can compile and run successfully. You’ll get the following output:
T? defaultValue = default(T);
T? defaultValue = default;
13.P4 Can you predict the output of the following code segment?
Sample.PrintDefault<MyClass>();
Sample.PrintDefault <MyStruct> ();
class MyClass
{
int _flag;
string _name;
public override string ToString()
{
string temp = $"_flag: {_flag} and _name: {_name}";
if (_name != null)
return temp;
else
return $"_flag: {_flag} and _name: null";
}
}
struct MyStruct
{
int _flag;
string _name;
public override string ToString()
{
string temp= $"_flag: {_flag} and _name: {_name}";
if (_name != null)
return temp;
else
return $"_flag: {_flag} and _name: null";
}
}
class Sample
{
internal static void PrintDefault<T>()
{
T? defaultValue = default;
string? printMe = (defaultValue == null) ? "null" :
defaultValue.ToString();
Console.WriteLine($"The default value of {typeof(T)}
is {printMe}.");
}
}
Answer:
This code can compile and run successfully. You’ll get the following output:
Explanation:
You may note that Console.WriteLine simply invokes the ToString()
method. This is why to show you the default values inside a class and struct, I have
overridden the ToString() method .
interface IVehicle<T>
{
T GetColor(T color);
}
class Car<T> : IVehicle<T>
{
public T GetColor(T color) => color;
public void Describe() => Console.WriteLine("This is a
car.");
}
Answer:
Yes, this code can compile and run successfully. You’ll get the following output:
This is a car.
Its color is green.
Explanation:
You can see that similar to a generic class, you can have a generic interface. In this
program, you have two methods, called GetColor(..) and Describe(). It
describes that a generic interface can contain nongeneric methods too.
When you implement a generic interface method, you can apply the same approach
that you follow to implement a nongeneric interface method. This program demonstrates
this fact. The Microsoft documentation ( https://docs.microsoft.com/en-
us/dotnet/csharp/programming-guide/generics/generic-
interfaces ) also says that the rules of inheritance that apply to classes also apply to
interfaces.
13.P6 Suppose you’ve got the following two interfaces:
interface IColor<T> { }
interface IBrand<T, U> { }
// Segment 1:
class Car1<T> : IColor<T> { }
// Segment 2:
class Car2<T> : IBrand<T, U> { }
// Segment 3:
class Car3<T> : IBrand<T, string> { }
// Segment 4:
class Car4<U> : IBrand<T,int> { }
// Segment 5:
class Car5<T> : IBrand<string, int> { }
// Segment 6:
class Car6 : IColor<string> { }
// Segment 7:
class Car7 : IBrand<double,string> { }
Answer:
Only segments 2 and 4 will not compile.
For segment 2, you’ll see the following error:
Explanation:
The error messages are self-explanatory: for segment 2, Car2 doesn’t include the
parameter U, and for segment 4, Car4 doesn’t include the parameter T.
For segment 1 and segment 3, Car1 and Car3 have the required parameters,
respectively.
For segments 5, 6, and 7, there were no issues at all because in these cases, the
respective classes worked on interfaces whose constructions are closed. If you have any
confusion, review the following terms again: Sample<T> is an open constructed type,
but when you write something like Sample<int> or Sample<string>, it is a
closed constructed type. A generic class can inherit from concrete, closed constructed, or
open constructed base classes.
// Employees
Employee e1 = new("Suresh", 1.5);
Employee e2 = new("Kate", 5.2);
Employee e3 = new("John", 7);
// Employee StoreHouse
EmployeeStoreHouse<Employee> empStore = new ();
empStore.AddToStore(e1);
empStore.AddToStore(e2);
empStore.AddToStore(e3);
interface IEmployee
{
}
class Employee : IEmployee
{
string _name;
double _yearOfExp;
public Employee(string name, double exp)
{
_name = name;
_yearOfExp = exp;
}
public override string ToString()
{
return $"Name: {_name} Exp:{_yearOfExp}";
}
}
class EmployeeStoreHouse<T> where T : IEmployee
{
readonly List<Employee> database = new();
public void AddToStore(Employee element)
{
database.Add(element);
}
public void DisplayStore()
{
Console.WriteLine("The current database:");
foreach (Employee emp in database)
{
Console.WriteLine(emp.ToString());
}
}
}
Answer:
This code can compile and run successfully. You’ll get the following output:
//interface IEmployee
//{
//
//}
Other parts of the program are unchanged. Can you compile the code now?
Answer:
No. You’ll receive the following compile-time errors:
Explanation:
You can see these issues due to the generic constraint, which is described as follows:
POINTS TO REMEMBER
In general, you’ll often notice the use of the following constraints:
where T: struct means that type T must be a value type. (Remember that
struct is a value type.)
where the T: class means that type T must be a reference type.
(Remember that class is a reference type.) In a nullable context in C# 8.0 or later, T
must be a non-nullable reference type.
where T: class? means that type T must be a reference type (either nullable
or non-nullable. (Remember that class is a reference type.)
where T: ISample means that type T must implement the ISample
interface .
where T: new() means that type T must have a default (parameterless)
constructor. (If you use it with other constraints, place it at the last position.)
where T: S means that type T must be derived from another generic type S. It
is sometimes referred to as a naked type constraint.
There are other constraints also, such as where T: default, where T:
unmanaged, where T:<interface_name>?, etc. If interested, you can learn
about them from at https://docs.microsoft.com/en-
us/dotnet/csharp/programming-guide/generics/constraints-
on-type-parameters .
Starting with C# 7.3, you can also use enum constraints. So, you can use where
T: System.Enum wherever applicable.
// Employees
Employee e1 = new("Suresh", 1.5);
Employee e2 = new("Kate", 5.2);
Employee e3 = new("John", 7);
// Employee StoreHouse
EmployeeStoreHouse<Employee> empStore = new
EmployeeStoreHouse<Employee>();
empStore.AddToStore(e1);
empStore.AddToStore(e2);
empStore.AddToStore(e3);
interface IEmployee
{
}
class Employee
{
string _name;
double _yearOfExp;
//public Employee() { }
public Employee(string name, double exp)
{
_name = name;
_yearOfExp = exp;
}
public override string ToString()
{
return $"Name:{_name} Exp:{_yearOfExp}";
}
Explanation:
Notice that the public constructor was commented out. To remove this error, you
have to uncomment it, because you have declared the generic constraint as follows:
// Employees
Employee e1 = new();
Employee e2 = new();
// Employee StoreHouse
EmployeeStoreHouse<Employee> empStore = new();
empStore.AddToStore(e1);
empStore.AddToStore(e2);
class Employee
{
public Employee() { }
// Some other code, if any
}
class EmployeeStoreHouse<T> where T : new(),Employee
{
readonly List<Employee> database = new();
public void AddToStore(Employee element)
{
database.Add(element);
}
// Some other code, if any
}
Answer:
No. You’ll receive the following compile-time error:
Additional Note:
The error message is self-explanatory. To remove this error, you need to place the
new() constraint as follows:
It is also important to note that in this program, you could write the following
constraint too:
// Some code
class Employee
{
readonly int _id;
public Employee(int id)
{
_id = id;
}
// Some other code, if any
}
class EmployeeStoreHouse<T> where T : class, new(int)
{
readonly List<Employee> database = new();
public void AddToStore(Employee element)
{
database.Add(element);
}
// Some other code, if any
}
Answer:
No. You’ll see multiple compile-time errors; one of them is as follows:
So, it is important to note that you cannot use a parameterized constructor constraint
in a situation like this. The following code raises these errors:
Additional Note:
In the context of the T: class constraint, you should remember Microsoft’s
guidelines whenever applicable. This guideline (see
https://docs.microsoft.com/en-us/dotnet/csharp/programming-
guide/generics/constraints-on-type-parameters ) specifies the
following:
When applying the where T: class constraint, avoid the == and != operators on
the type parameter because these operators will test for reference identity only,
not for value equality.
If you must test for value equality, the recommended way is to also apply the
where T: IEquatable<T> or where T: IComparable<T> constraint and
implement the interface in any class that will be used to construct the generic
class.
13.P12 Can you compile the following code?
interface IEmployee
{
// Some code
}
class Employee: IEmployee
{
}
class Person
{
public Person()
{
// some code
}
// Some other code, if any
}
class EmployeeStoreHouse<T,U> where T : Employee?,
IEmployee?
where U: new()
{
// Some other code, if any
}
Answer:
Yes, this code will compile successfully.
Additional note:
This program segment shows how to apply constraints to multiple parameters. It also
describes how to apply multiple constraints on a single parameter.
Covariance
13.P13 Can you compile the following code?
Console.WriteLine("Normal usage:");
Func<Vehicle> vehicle = Vehicle.GetOneVehicle;
vehicle();
Func<Bus> bus=Bus.GetOneBus;
bus();
Console.WriteLine("Testing covariance:");
vehicle = bus;
vehicle();
class Vehicle
{
public static Vehicle GetOneVehicle()
{
Console.WriteLine("Making a vehicle.");
return new Vehicle();
}
}
class Bus : Vehicle
{
public static Bus GetOneBus()
{
Console.WriteLine("Making a bus.");
return new Bus();
}
}
Answer:
Yes, this code can compile and run successfully. You’ll get the following output:
Normal usage:
Making a vehicle.
Making a bus.
Testing covariance:
Making a bus.
Explanation:
Because of the covariance support, the following line did not raise any compile-time
errors:
vehicle = bus;
Explanation:
In this example, you have seen the built-in construct IEnumerable<T>. This is a
widely used interface in C# programming. Now you can use a foreach loop to do
something meaningful on each item in a collection and process them one by one. Nearly
every class in the .NET Framework that contains multiple elements implements this
interface. For example, the commonly used List class also implements this interface.
Again, in this program, I’ve used Vehicle as the parent class and Bus as the
derived class. Let me retrieve the definition of generic interface IEnumerable from
Visual Studio, as shown here:
public interface IEnumerable<out T> : IEnumerable
{
//
// Summary:
// Returns an enumerator that iterates through
the
// collection.
//
// Returns:
// An enumerator that can be used to iterate
// through the collection.
new IEnumerator<T> GetEnumerator();
}
Have you noticed the out parameter? It means that T is covariant. Why? See the
official documentation ( https://docs.microsoft.com/en-
us/dotnet/csharp/language-reference/keywords/out-generic-
modifier ) that says the following:
For generic type parameters, the out keyword specifies that the type parameter is
covariant. You can use the out keyword in generic interfaces and delegates.
The compiler allows you to convert from buses to vehicles because the type
parameter is covariant. Let us think about this conversion. The out keyword on T
ensures that T is used only in output positions, which means that you want to use it for
the return type of a method. This conversion is also type-safe because instead of a Bus,
you cannot use a different type such as a Train. Why? Notice that there’s no way to
feed a Train into an interface where T can appear only in output position s.
Note In many real-life applications, it’s a common practice to use methods that
return IEnumerable<T>. This is helpful when you do not want to disclose the
actual concrete type to others and to have the ability to loop through the items (not to
modify the collection, though, for example, to add or remove items).
Contravariance
13.P15 Can you compile the following code?
Normal usage:
The ShowMe() of the Vehicle is called.
The ShowMe() of the Bus is called.
Testing contravariance now.
The ShowMe() of the Bus is called.
Explanation:
The following segment of code is easy to understand because it matches the delegate
signature. (See the output of the previous program if required.)
Now comes the interesting part, which is the opposite of covariance. Notice the
following assignment:
This assignment doesn’t raise any compilation error because this time, I’m using a
contravariant delegate.
For generic type parameters, the in keyword specifies that the type parameter is
contravariant. You can use the in keyword in generic interfaces and delegates.
But it is important to note that if you do not make the delegate contravariant using
the in parameter, this assignment will cause the compile-time error saying the
following:
Answer:
This code can compile and run successfully. You’ll get the following output:
Normal usage:
The ShowMe() of the Vehicle is called.
The ShowMe() of the Bus is called.
Using contravariance now.
The ShowMe() of the Bus is called.
Explanation:
13.15 and 13.P16 are equivalent programs. The only difference is that this time you
see me using the built-in Action delegate , which is contravariant. For your reference,
let me show you its definition:
bus = vehicle;
Console.WriteLine(bus);
bus.Display(aBus);
class Vehicle
{
public override string ToString()
{
return "One vehicle";
}
}
class Bus : Vehicle
{
public override string ToString()
{
return "One bus";
}
}
Answer:
Yes, this code can compile and run successfully. You’ll get the following output:
Implementor[Vehicle]
One vehicle
Implementor[Bus]
One bus
Implementor[Vehicle]
One bus
Explanation:
The bus = vehicle; line of code worked due to the contravariance. If you do
not make this interface contravariant and use something like interface
IContraInterface<A>, you’ll receive the following compile-time error for this
assignment:
Let us think about this conversion one more time. Notice the interface definition in
this program closely:
The in keyword on A ensures that A is used only in input positions, which means
that you want to use it for the method parameters, but not for the return type. Since no
member in IContraInterface outputs an A, you did not face any trouble by casting
a vehi cle to a bus, and this is why the C# compiler allows this conversion.
POINTS TO NOTE
Covariance and contravariance in generic type parameters are applicable for reference
types, but not for value types. In addition, you may have a question: “When I see
something like Func<in T, out TResult> or Func<in T1, in T2,
out TResult>, how should I interpret these definitions?” The answer is that these
definitions tell that the Func delegates have a covariant return type but the
contravariant parameter types.
interface IIdenticalEmployee<T>
{
string CompareWith(T obj);
}
class Employee : IIdenticalEmployee<Employee>
{
string _name;
int _id;
public Employee(string name, int id)
{
_name = name;
_id = id;
}
public string CompareWith(Employee obj)
{
if (obj == null)
{
return "Cannot compare with a null object.";
}
else
{
if (_name == obj._name && _id == obj._id)
{
return "Identical Employees.";
}
else
{
return "Different Employees.";
}
}
}
}
Answer:
Yes, this code can compile and run successfully. You’ll get the following output:
Explanation:
When you compare two instances of a class, either you use the built-in constructs or
you write a custom comparison method. When you are interested in using built-in
constructs, you have multiple options. For example, you can use either the CompareTo
method of IComparable<T> or the Equals method of IEquitable<T>. But in
many cases, you may want to write a custom comparison method . I’ve done the same in
this program.
This program simply checks whether the _name and _id are the same for two
employees. If both match, it says that employees are the same. (Using the word same, I
mean only the content of these objects are the same, but not the reference to the heap.)
This program also shows that a type can name itself as the concrete type when it
closes the type argument. So, you see a self-referencing generic declaration in this
program.
Console.WriteLine("---13.P19---");
Sample<int, double> sample1 = new();
sample1.Display(5, 10.5);
Explanation:
It may appear to you that you have two overloaded versions of the Display
method because the order of the generic type parameters are different. But there is
potential ambiguity, which you can see when you exercised the following code
segments:
Explanation:
This program shows you two important characteristics of generics .
You can overload a generic method.
You can call a generic method without using the type argument because the compiler
can infer it.
So, the following lines are equivalent:
sample.Display(5);// Ok
sample.Display<int>(5);// Also OK
sample.Display(5, 10.7);// OK
sample.Display<int,double>(5, 10.7);// Also OK
In this context, it’ll be useful for you to note the following information from
Microsoft’s documentation ( https://docs.microsoft.com/en-
us/dotnet/csharp/programming-guide/generics/generic-methods
):
The compiler can infer the type parameters based on the method arguments you
pass in; it cannot infer the type parameters only from a constraint or return
value. Therefore type inference does not work with methods that have no
parameters. Type inference occurs at compile time before the compiler tries to
resolve overloaded method signatures. The compiler applies type inference logic
to all generic methods that share the same name. In the overload resolution step,
the compiler includes only those generic methods on which type inference
succeeded.
13.P21 Can you compile the following code?
class Parent
{
public virtual void Display<T>(T p1)
{
Console.WriteLine("Parent's Display");
}
}
class Derived : Parent
{
public override void Display<T>(T p1)
{
Console.WriteLine("Derived's Display");
}
}
Answer:
Yes, this code can compile and run successfully. You’ll get the following output:
Parent's Display
Derived's Display
Derived's Display
}
Answer:
This code can compile and run successfully. You’ll get the following output:
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
V. Sarcar, Test Your Skills in C# Programming
https://doi.org/10.1007/978-1-4842-8655-5_14
14. Threading
Vaskaran Sarcar1
Note Once you understand multithreading, you should learn about asynchronous
programming. These two concepts are closely related. Keeping in mind the target
readers, I will not be discussing asynchronous programming in this book. But, if
you’re interested, you can learn about it from the book Design Patterns in C# (2nd
edition), which is published by the same publisher. A big chapter is included in that
book to help you understand asynchronous programming in depth.
Theoretical Concepts
This section includes the theoretical questions and answers on multithreading in C#.
Threading Fundamentals
14.T1 What is a thread?
Answer:
Most of the programs you have seen so far probably had a single sequential flow of
control. In other words, once the program starts executing, it goes through all the
statements sequentially until the end. As a result, at any particular moment, there is
only one statement that is under execution. A thread is similar to a program. It has a
single flow of control. It also has a body between the starting point and endpoint, and it
executes the commands sequentially. So, in short, you can define a thread as an
independent execution path of a program. You may note that each program has at least
one thread.
14.T2 What is the key advantage of using a multithreaded program over a
single-threaded program?
Answer:
Since you can execute the independent segments of code in separate threads, you
can complete a task faster .
In a single-threaded environment, if the thread blocks, the entire program is halted,
which is not the case in a multithreaded environment. In addition, you can reduce the
overall idle time by making efficient use of the central processing unit (CPU) . For
example, consider the case of sending a large amount of data over a network. In this
case, when one part of your program is sending a large block of data over the network,
another part of the program can accept the user inputs, and still another part of your
program can validate those inputs and prepare the next block of data for sending.
In short, since the independent pieces of code do not affect each other, you can run
them in parallel to complete a job much faster. This is the core aim of multithreading.
14.T3 What do you mean by multithreading in programming?
Answer:
It’s a programming paradigm to have a program divided into multiple subprograms
(or parts) that can be implemented in parallel. But the fact is that unless you structure
your code to run on multiple processors, you are not using the machine’s full
computing potential. To visualize this, you can refer to Figure 14-1. This figure
demonstrates a simple scenario in a multithreaded program, where a parent thread,
called the Main thread, creates two more threads, threadOne and threadTwo,
and all these threads are running concurrently.
Figure 14-1 In a multithreaded program , the main thread creates two more threads, threadOne and threadTwo. All
of them are running concurrently
14.T4 In Figure 14-1 , I’m seeing the term context switching . What does it
mean?
Answer:
In general, many threads can run in parallel on your computer. Actually, the
computer allows one thread to run in one processor for some time, and then it can
switch to a different one. This decision is made based on different factors. Normally, all
threads have equal priority, and switching among them is done nicely. Switching
between threads is termed context switching . It also enables you to store the state of
the current thread (or process) so that you can resume the execution from this point
later.
14.T5 Can you explain the difference between multithreading and
multitasking?
Answer:
In simple words, multiprocessing involves more than one processor, and
multithreading is used for a single process. When the OS divides the processor
execution time among different applications, it is called multitasking , and when the
OS divides the execution time among different threads within a single application, it is
called multithreading . This is why multithreading can be considered a special case of
multitasking.
14.T6 I have a computer that has only one processor. Can I write a
multithreaded program using this computer?
Answer:
Theoretically, the answer is yes. In this case, the processor can switch among these
subprograms (or, segments of code) very fast. As a result, it appears to human eyes that
this code is executed simultaneously. In earlier days, most computers had a single core
and, in those systems, concurrent threads shared the CPU cycles, but in actuality, they
could not truly run in parallel.
14.T7 How can multithreading help me if I have a multicore system?
Answer:
Using the concept of multithreading , you can reduce the overall idle time by
efficient use of the CPU. But if you have multiple processors, you can run multiple
threads concurrently. As a result, you can further enhance the speed of your program.
Thread Safety
14.T8 I often hear about the term thread safety . What does it mean?
Answer:
When you have multiple threads that access a shared resource, you need to ensure
thread safety. For example, consider a situation when one thread is trying to read the
data from a file and another thread is still writing or updating in the same file. If you
cannot manage the correct order, you may get surprising results. The concept of
synchronization is useful in a similar situation.
14.T9 You said, “If you cannot manage the correct order, you may get
surprising results.” Can you give another example to illustrate this?
Answer:
In 14.P9, you can see a program that shows the problem of a nonsynchronized
program. For now, let us consider a simple example to demonstrate the importance of
thread safety.
Let’s say you have an integer variable that is initialized with a value, say, 0.
Further, assume that there are two threads; each of them will exercise some code to
increment this value by 1 and save the result. Let's call these threads Thread-1 and
Thread-2. Since both these threads increment the value, you expect to see the final
value as 2 inside this integer variable.
Now, consider the following situation, which can produce a different value:
Thread-1 reads the current value from the variable that is 0 now. So, it increments
the value to 1 and is about to save this value.
Before Thread-1 saves this value, Thread-2 starts reading the current value
(which is the last saved value and still 0) from the variable. It also increments the
value to 1 and is about to save this value.
Thread-1 saves the new value (1) back into the variable.
Now, Thread-2 also saves the new value back (1) into the variable.
As a result, the final value is 1.
POINTS TO REMEMBER
There are many ways through which you can coordinate multiple thread executions.
Some common terms are frequently used in this context. For your easy
understanding, let me explain them in simple words: the segment of code that you
want to guard against simultaneous access from multiple threads is called the critical
section. At any given moment, you allow only one thread to work in the critical
section. This principle is termed mutual exclusion. You can enforce this principle
through a synchronization object such as a mutex or semaphore. Often you will see
the use of locks to ensure thread safety. Locks are a common mechanism to avoid
accidental modification of shared resources due to simultaneous access of multiple
threads in a shared location. You can say that a mutex enforces a lock-based
mechanism to provide support for mutual exclusion.
You may also note that you can make a lock at your convenience. For example, if
you deal with a static method, you could even write something like the following:
Deadlock
14.T12 How multiple threads can cause a deadlock?
Answer:
When multiple threads are waiting for each other to complete a job but none of
them can complete the job, you observe a hanging state. You can call it a deadlock. You
can compare this situation with the following real-life examples:
You can’t get a job without experience, but you can’t get experience without a job.
After a fight between two close friends, each of them expects the other to initiate the
friendship again.
Author’s Note: In 14.P12, you’ll see a program that can reach the deadlock state.
POINTS TO REMEMBER
In programming, without a proper synchronization technique, you may notice some
surprising output (for example, you may notice some corrupted data), but with
improper use of synchronization, the situation can become worse: you can encounter
a deadlock.
ThreadPool Class
14.T13 What is the purpose of the ThreadPool class?
Answer:
Creating threads directly in a real-world application is normally discouraged. Some
key reasons behind this are as follows:
Maintaining too many threads incurs tough and costly operations.
A big amount of time is wasted due to context switching, instead of doing the real
work.
When you start a thread, to work things properly at least some amount of time is
invested. This time can be saved if you use a prebuilt thread.
To avoid directly creating threads, C# gives you the facility to use the built-in
ThreadPool class. Using this class, you can use the already existing threads that can
be reused to serve your purpose. The ThreadPool class is effective to maintain the
optimal number of threads in your application. It can also help you write top-quality
asynchronous programs. A discussion of asynchronous programming is beyond the
scope of this book. Still, I suggest you note the following lines from Microsoft’s online
documentation ( https://docs.microsoft.com/en-
us/dotnet/api/system.threading.threadpool?view=net-6.0 ):
The thread pool enables you to use threads more efficiently by providing your
application with a pool of worker threads that are managed by the system.
Beginning with the .NET Framework 4, the default size of the thread pool for a
process depends on several factors, such as the size of the virtual address
space. A process can call the GetMaxThreads method to determine the number
of threads. The number of threads in the thread pool can be changed by using
the SetMaxThreads method. Each thread uses the default stack size and runs at
the default priority.
The thread pool provides new worker threads or I/O completion threads on
demand until it reaches the maximum for each category. When a maximum is
reached, the thread pool can create additional threads in that category or wait
until some tasks are complete. Beginning with the .NET Framework 4, the
thread pool creates and destroys worker threads in order to optimize
throughput, which is defined as the number of tasks that complete per unit of
time. Too few threads might not make optimal use of available resources,
whereas too many threads could increase resource contention.
Bonus Q&A
14.T17 How is the Yield() method different from the Sleep() method?
Answer:
The Yield() method is useful for the current processor. It causes the current
thread to allow other threads to complete their work. However, if there is no such
candidate, your thread will soon be rescheduled and will continue the task. Microsoft (
https://docs.microsoft.com/en-
us/dotnet/api/system.threading.thread.yield?
redirectedfrom=MSDN&view=net-
6.0#System_Threading_Thread_Yield ) ensures this by saying the
following:
Yielding is limited to the processor that is executing the calling thread. The
operating system will not switch execution to another processor, even if that
processor is idle or is running a thread of lower priority. If there are no other
threads that are ready to execute on the current processor, the operating system
does not yield execution, and this method returns false.
It suspends the current thread for a specified amount of time. So, compared to
Yield(), it causes less CPU utilization. But you may note a special case that is
mentioned as follows ( https://docs.microsoft.com/en-
us/dotnet/api/system.threading.thread.sleep?
redirectedfrom=MSDN&view=net-6.0#overloads ):
Programming Skills
By using the following questions, you can test your programming skills in C#. Note
that if I do not mention the namespace, you assume that all parts of the program belong
to the same namespace.
Basic Concepts
14.P1 Predict the output of the following code segment :
Thread.CurrentThread.Name = "main";
Console.WriteLine($"The {Thread.CurrentThread.Name} thread
has started.");
Analysis:
I showed two possible outputs, but still, the output may vary in your case. It is
common in thread programming because your operating system employs context
switching as per the design. Later you’ll see that you can employ some special
mechanisms to control the execution order.
I also want you to note that the following lines are equivalent:
POINTS TO NOTE
In many contexts (particularly in UI applications), you’ll see the term worker
thread. It is used to describe another thread that is different from the current thread.
Technically, it is a thread that runs in the background, though no one claims that this
is the true definition. In the context of C#, Microsoft says this: by default, a .NET
program is started with a single thread, often called the primary thread. However,
the program can create additional threads to execute code in parallel or concurrently
with the primary thread. These threads are often called worker threads. (See
https://docs.microsoft.com/en-
us/dotnet/standard/threading/threads-and-threading .)
Thread.CurrentThread.Name = "main";
Console.WriteLine($"The {Thread.CurrentThread.Name} thread
has started.");
I wrote these statements inside the Main() method. So, once the original thread
passes through these statements, it waited for threadOne and threadTwo to finish
their jobs and effectively join the execution of these child threads. I have used sleep
statements inside the Sample class methods to pause them for a small amount of time.
It helps you to visualize the effect of multithreading in this program easily.
Thread.CurrentThread.Name = "main";
Console.WriteLine($"The {Thread.CurrentThread.Name} thread
has started.");
Explanation:
In the previous cases, you’ve seen the usage of the ThreadStart delegate . As a
result, you were not able to deal with methods that accept parameters. But methods
with parameters are very common in programming. So, this time you will see the use
of the ParameterizedThreadStart delegate .
Confused? OK, let me retrieve the definition of the
ParameterizedThreadStart delegate from the Visual Studio IDE. It is as
follows:
//
// Summary:
// Represents the method that executes on a
// System.Threading.Thread.
//
// Parameters:
// obj:
// An object that contains data for the thread
// procedure.
public delegate void ParameterizedThreadStart(object?
obj);
You can see that this delegate accepts an object parameter, and its return type is
void. Since the parameter is an object, you can use it for any type as long as you
can apply the cast properly to the right type.
Now you understand that ExecuteMethod3 matches this delegate’s signature.
So, instead of writing the short version:
14.P4 Suppose, in the previous program ( 14.P3 ), that I replace the following
line:
Explanation:
The error is self-explanatory. ExecuteMethod1 matches the signature of the
ThreadStart delegate, but not ExecuteMethod3. Since ExecuteMethod3
accepts a parameter, you need to use (or match) the
ParameterizedThreadStart delegate.
14.P5 Can you compile the following code?
Thread.CurrentThread.Name = "main";
Console.WriteLine($"The {Thread.CurrentThread.Name} thread
has started.");
class Sample
{
public static void ExecuteMethod1()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"The
{Thread.CurrentThread.Name} from
ExecuteMethod1
prints {i}");
Thread.Sleep(1);
}
}
public static void ExecuteMethod4(object? limits)
{
Boundaries? boundaries = limits as Boundaries;
if (boundaries != null)
{
int lowerLimit = boundaries.lowerLimit;
int upperLimit = boundaries.upperLimit;
if (lowerLimit <= upperLimit)
{
for (int i = lowerLimit; i < upperLimit;
i++)
{
Console.WriteLine($"The
{Thread.CurrentThread.Name} from
ExecuteMethod4 prints {i}");
Thread.Sleep(1);
}
}
else
{
Console.WriteLine("The lower limit cannot
be
greater than the upper limit.");
}
}
}
}
Answer:
Yes, this code can compile and run successfully. Here is the possible output:
Additional Note:
You can see that there is a class called Boundaries. I created an instance of it
and passed 7 and 11 as arguments. Similarly, you can pass as many arguments as you
want to construct an object and then pass it to a method that matches the
ParameterizedThreadStart delegate .
ExecuteMethod4 is a little bit bigger compared to ExecuteMethod1. This is
because I have put a guard to block some invalid data. So, if you write something like
this in the output:
Using Lambdas
14.P6 Can you compile the following code?
Explanation:
In the previous examples, you saw how to run methods on different threads. But
each of these methods had a void return type . This program shows you a way to deal
with the methods that have non-void return types. You can see that I have used a
lambda expression in this context.
Another point is that though the following line was purely optional, I wanted you to
see the use of ManagedThreadId:
Thread.CurrentThread.Name = "main";
Console.WriteLine($"The {Thread.CurrentThread.Name} thread
with priority {Thread.CurrentThread.Priority} is
executing now.");
Here is another possible output where the main thread finishes after thread-
one:
Explanation:
At the time of this writing, a thread in C# can have the following priorities:
Lowest, BelowNormal, Normal, AboveNormal, and Highest. From
one of these possible outputs, you can see that though I have set the child thread
priority higher than the main thread, there is no guarantee that the child thread will
finish before the main thread. Several other factors may determine this output.
Conceptually, the priority determines how frequently a thread can get the CPU’s
time. In theory, the higher-priority threads get more CPU time than the lower-priority
threads, and in preemptive scheduling, they can preempt the lower-priority threads.
Actually, you need to consider many other factors. Consider a case where a low-
priority thread does a very short task and a high-priority thread does a long-running
task. Though the high-priority thread gets more chances to complete its task, it is
possible that the low-priority thread gets a single chance to execute and it finishes
immediately. Lastly, how task scheduling is implemented in an operating system also
matters because CPU allocation depends on this too. This is why you shouldn’t depend
entirely on priorities to predict the output.
Explanation:
By default, a thread is a foreground thread. But I converted the child thread to a
background thread by setting the IsBackground property to true. But if you
comment out the following line in this example as follows:
// childThread.IsBackground = true;
Console.WriteLine("Exploring a non-synchronized
version.****");
Thread.CurrentThread.Name = "main thread";
Console.WriteLine($"The {Thread.CurrentThread.Name} is
executing now.");
Author’s Note: This output may vary in each run in your system. To get the same
output, you may need to execute the application multiple times.
POINT TO NOTE
From this output segment, you can see that Child thread-2 entered the shared
location before Child thread-1 finishes its execution. When you deal with
shared data, this kind of situation is dangerous. Why? For example, suppose one
thread is writing some data while another thread is reading the data. As a result, you
can get inconsistent results.
}
// Waiting for Child Thread-1 to finish.
threadOne.Join();
// Waiting for Child Thread-2 to finish.
threadTwo.Join();
Console.WriteLine($"The {Thread.CurrentThread.Name} is
about to finish.");
class SharedResource
{
private readonly object myLock = new();
public void ExecuteSharedMethod()
{
lock (myLock)
{
Console.WriteLine($"
{Thread.CurrentThread.Name} has
entered the shared
location.");
Thread.Sleep(1000);
Console.WriteLine($"{Thread.CurrentThread.Name}
exits.");
}
}
}
Answer:
This code can compile and run successfully. Here is some possible output:
Explanation:
Now you see a synchronized program. From this output segment, you can see that
once a thread enters into the shared location and finishes its job, no other thread can
enter into that location. I have introduced a random number generator to show you that
either Child thread-1 or Child thread-2 can enter into the shared location
first.
14.P11 In the previous program( 14.P10 ), if you replace the following code
block:
lock (myLock)
{
Console.WriteLine($"{Thread.CurrentThread.Name} has
entered
the shared location.");
Thread.Sleep(1000);
Console.WriteLine($"{Thread.CurrentThread.Name}
exits.");
}
try
{
Monitor.Enter(myLock);
Console.WriteLine($"{Thread.CurrentThread.Name} has
entered
the shared location.");
Thread.Sleep(1000);
Console.WriteLine($"{Thread.CurrentThread.Name}
exits.");
}
finally
{
Monitor.Exit(myLock);
}
what will be the change in the output?
Answer:
You will see the equivalent code. The Monitor class members are also used to
implement synchronization. You’ve seen the use of a lock in 14.P10. So, you
understand that it is a shortcut for Monitor’s Entry and Exit methods with the
try and finally blocks.
POINTS TO NOTE
The Monitor class has some useful methods. For example, in this class, you can
see the presence of many other methods such as Pulse, PulseAll, Wait,
and TryEnter. Some of these methods have different overloaded versions too.
Here are the simple descriptions of some of these methods:
Wait(): Using this method, a thread can wait for other threads to notify.
Pulse(): Using this method, a thread can send notifications to another thread.
PulseAll(): Using this method, a thread can notify all other threads within a
process.
TryEnter(object obj): This method returns the Boolean value true if
the calling thread can obtain an exclusive lock on the desired object; otherwise, it
will return false. Using a different overloaded version of this method, you can
specify a time limit, in which you attempt to get an exclusive lock on the desired
object.
Exploring Deadlocks
14.P12 Can you compile the following code?
class SharedResource
{
private object firstLock = new();
private object secondLock = new();
public void MethodOne()
{
lock (firstLock)
{
Console.WriteLine($"
{Thread.CurrentThread.Name}
enters into MethodOne(Part-1).");
Console.WriteLine($"
{Thread.CurrentThread.Name}
exits MethodOne(Part-1).");
lock (secondLock)
{
Console.WriteLine($"
{Thread.CurrentThread.Name}
has entered MethodOne(Part-2).");
Thread.Sleep(500);
Console.WriteLine($"
{Thread.CurrentThread.Name}
exits MethodOne(Part-2).");
}
}
}
public void MethodTwo()
{
lock (secondLock)
{
Console.WriteLine($"
{Thread.CurrentThread.Name}
enters into MethodTwo(Part-I).");
Console.WriteLine($"
{Thread.CurrentThread.Name}
exits MethodTwo(Part-1).");
lock (firstLock)
{
Console.WriteLine($"
{Thread.CurrentThread.Name}
has entered MethodTwo(Part-II).");
Thread.Sleep(500);
Console.WriteLine($"
{Thread.CurrentThread.Name}
exits MethodTwo(Part-2).");
}
}
}
}
Answer:
This code can compile. Here is some possible output:
But this program may hang (or freeze) when you run it. When this program hangs,
you’ll see only these lines in your output:
Figure 14-2 A deadlock situation: thread 15808 is waiting for thread 21796 to release the lock and vice ve rsa
Using ThreadPool
14.P13 Can you compile the following code?
Thread.CurrentThread.Name = "main";
Console.WriteLine($"The {Thread.CurrentThread.Name} thread
has started.");
Thread threadOne = new(Sample.ExecuteMethod1);
threadOne.Name = "thread-one";
Console.WriteLine($"Starting {threadOne.Name} shortly.");
// threadOne starts
threadOne.Start();
ThreadPool.QueueUserWorkItem(Sample.ExecuteMethod3);
Console.WriteLine("Control comes at the end of the Main
method.");
class Sample
{
public static void ExecuteMethod1()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"The
{Thread.CurrentThread.Name} from
ExecuteMethod1
prints {i}");
Thread.Sleep(1);
}
}
public static void ExecuteMethod3(object? number)
{
int upperLimit = Convert.ToInt32(number);
for (int i = upperLimit - 3; i < upperLimit; i++)
{
Console.WriteLine($"The
{Thread.CurrentThread.Name} from
ExecuteMethod3
prints {i}");
Thread.Sleep(1);
}
}
}
Answer:
Yes, this code can compile and run successfully. Here is some possible output:
Figure 14-3 A snapshot of the ThreadPool class from the Visual Studio 2022 IDE
// Summary:
// Queues a method for execution. The method executes
when
// a thread pool thread becomes available.
//
// Parameters:
// callBack:
// A System.Threading.WaitCallback that represents the
// method to be executed.
//
// Returns:
// true if the method is successfully queued;
// System.NotSupportedException is thrown
// if the work item could not be queued.
//
// Exceptions and other details are skipped here
public static bool QueueUserWorkItem(WaitCallback
callBack);
If you further investigate the method parameter, you’ll find that WaitCallBack
is a delegate with the following description:
//
// Summary:
// Represents a callback method to be executed by a
thread
// pool thread.
//
// Parameters:
// state:
// An object containing information to be used by the
// callback method.
public delegate void WaitCallback(object? state);
ThreadPool.QueueUserWorkItem(Sample.ExecuteMethod3);
or the following:
ThreadPool.QueueUserWorkItem(new WaitCallback(
Sample.ExecuteMethod3));
The remaining code is easy to understand. You can see that to run the
ExecuteMethod1 method, I created a thread, but for the ExecuteMethod3
method, I used a pooled thread.
Since you did not pass any argument for ExecuteMethod3, it took 0 as the
argument. You can test this by using the following line:
Console.WriteLine(Convert.ToInt32(null)); //prints 0
But if you want to pass any other valid argument, you can use the overloaded
version in this program:
ThreadPool.QueueUserWorkItem(Sample.ExecuteMethod3);
ThreadPool.QueueUserWorkItem(Sample.ExecuteMethod3,10);
Here is some sample output for this change (notice the changes in bold):
Thread.CurrentThread.Name = "main";
Console.WriteLine($"The {Thread.CurrentThread.Name} thread
has started.");
ThreadPool.QueueUserWorkItem(new
WaitCallback(Sample.ExecuteMethod));
Console.WriteLine("Control comes at the end of the Main
method.");
class Sample
{
public static void ExecuteMethod()
{
// Some code
}
}
Answer:
No. You’ll receive the following compile-time error:
CS0123 No overload for 'ExecuteMethod' matches delegate 'WaitCallback'
Explanation:
If needed, see 14.P13’s explanation again. Note that the delegate signature does not
mat ch the method signature in this program.
Thread.CurrentThread.Name = "main";
Console.WriteLine($"The {Thread.CurrentThread.Name} thread
has started.");
ThreadPool.QueueUserWorkItem(Sample.ExecuteMethod3,
cts.Token);
//ThreadPool.QueueUserWorkItem(new
// WaitCallback(Sample.ExecuteMethod3),
// cts.Token); // Also Ok
Thread.Sleep(1000);
In this case, you’ll see some output something like the following (notice the change
in bold):
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
V. Sarcar, Test Your Skills in C# Programming
https://doi.org/10.1007/978-1-4842-8655-5_15
15. Miscellaneous
Vaskaran Sarcar1
Welcome to the final chapter of this book. You have seen many interesting
features in C# 10 and its preceding versions. At the time of this writing, the C#
11 preview is newly available too. So, let’s look at some of the newest features of
C#. In addition, we’ll cover some important topics that wouldn’t fit into earlier
chapters that can help you write better programs in the future. This chapter
covers the following topics:
Some of the latest changes in C# (I’ll limit the discussion to those topics that
are discussed in the first 14 chapters of this book)
switch expressions with discard patterns
Partial classes and methods
Operator overloading
Use of a record type in brief
Let us review and test your understanding of these topics now.
Theoretical Concepts
This section includes the theoretical questions and answers on miscellaneous
topics.
Concept of Preview
15.T1 What do you mean by preview features?
Answer:
Preview features are not officially released for widespread use. They are
made available for customers who want to get early access to them, do some
experiments, and provide feedback about them. In general, you need to change
some settings before you test them. If the overall customer feedback of a preview
feature is not satisfactory, the feature may remain in the preview phase for a
longer period.
For example, I use the following settings in 15.P1, which you’ll see in the
“Programming Skills” section shortly:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Operator Overloading
15.T3 Can you overload operators in C#?
Answer:
Yes. But there are certain rules to follow. Note the following:
You use the operator keyword to declare an operator. The keyword
operator is followed by the operator symbol. Here is a sample:
Class Sample
{
public static Sample operator ++(Sample sample)
{
// Some code
}
// Some other code skipped
}
An operator declaration must satisfy the following rules:
You must include both a public modifier and a static modifier.
At least one input parameter of an operator (both unary and binary) must
have type T or T? where T is the type that contains the operator declaration.
In addition, all operators cannot be overloaded. Let me include some of them
that you saw earlier. For example, you cannot overload the default,
delegate, new, switch, typeof, and with operators.
Certain operators need to be overloaded in pairs. For example:
== and != operators
< and > operators
<= and >= operators
15.T4 It appears to me that anyone can misuse the concept when they
overload contradictory operations using operator overloading; for example,
I can use the ++ operator to decrement something. Is this correct?
Answer:
This is why you need to be careful. You should not implement any such
operation to make a bad design. In addition, you must note that C# does not
allow us to overload all the operators .
namespace Container
{
public partial class Sample
{
public void Method1()
{
// Some code
}
}
Note There are many restrictions on using a partial class and methods. For
example, you cannot apply the partial keyword to constructors, finalizers,
overloaded operators, property declarations, or event declarations. In addition,
at the time of this writing, the partial modifier is not available on delegate or
enumeration declarations. In this chapter, you’ll examine a few case studies
for partial classes and partial methods. If you’re interested, you can learn
more about them from https://docs.microsoft.com/en-
us/dotnet/csharp/programming-guide/classes-and-
structs/partial-classes-and-methods .
15.T8 I am seeing people have started using the record type, but you have
not discussed it in this book. Is there any specific reason for this? When
should I use a record type?
Answer:
This book is reviewing most of the important features and concepts in C# that
are used in everyday programming. To keep the book within a reasonable page
limit, discussions on the record type as well as other concepts such as
asynchronous programming and LINQ queries are absent from this book. If you
like this book, you’ll probably see many such discussions in an updated edition.
For a quick answer, let me remind you that class and objects are the
foundation of object-oriented programming. You use classes to focus on the
responsibilities and behavior of objects. You use the struct type to store small
data that can be copied efficiently. C# 9 introduced the record type, which is a
new reference type. You use it for value-based equality and comparison.
Starting with C# 10, you can also create the record struct type to define
records as value types. This is useful when you want features of records for a
type that is small enough to copy efficiently.
Programming Skills
By using the following questions, you can test your programming skills in C#.
Here we focus on preview features, operator overloading, partial classes, and
methods. Note that if I do not mention the namespace, you can assume that all
parts of the program belong to the same namespace.
Preview Features
When you read this book, some or all these preview features will be included in
the current channel. If you go through the discussions of these preview features,
you’ll have a better idea about how to test a preview feature in the future.
Once you follow the Visual Studio IDE’s suggestion, you’ll receive the
following output:
Explanation:
You have seen an example of using a raw string literal. This literal starts with
at least three double quotes (""") and ends with the same number of double
quotes.
This kind of string literal can include arbitrary text, including whitespace,
new lines, embedded quotes, and other special characters without requiring
escape sequences. You can see that I have applied string interpolation with raw
strings in this example.
Additional Note:
Following the online guidelines about C# language versioning, I added
<LangVersion>preview</LangVersion> in my projects to test the
preview features.
Note that
<EnablePreviewFeatures>True</EnablePreviewFeatures> can
also help you test the preview features in this context. It means that if you use the
following segment, this project can compile and run too:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<EnablePreviewFeatures>True</EnablePreviewFeatures>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
How does this setting work? Read the Microsoft guidelines at
https://docs.microsoft.com/en-
us/dotnet/fundamentals/code-analysis/quality-
rules/ca2252 .
Answer:
This code can compile and run successfully. You’ll get the following output:
Explanation:
Characters inside { and } can span multiple lines. Microsoft introduced this
feature to read string interpolations with longer C# expressions easily. You often
see them in switch expressions and LINQ queries.
Starting with C# 8.0, you can see the presence of switch expressions. If
you are unfamiliar with it, let me show you an equivalent program that shows the
switch-like semantics in a statement context.
//CheckNumber(75);
CheckNumber(40);
//CheckNumber(27);
}
}
Author’s Note: I have kept some commented lines to test the remaining
cases. You can test them once you download the source code from the Apress
website.
So, what is the difference between these two styles? You can see the
following in the new style (i.e., the switch expression shown earlier):
The variable name appears before the switch keyword. So, instead of the
switch(score), I could use the score switch.
Instead of using case >=40:, I have used >= only. This means that the
case and : elements were replaced with =>.
The default case is also replaced with the underscore (_ ) character. Basically,
I have used a discard pattern here. So, if you use int? score = null;
instead of int? score = 40;, you can see the following output:
Score: Remarks: Failed.
In short, this new switch syntax is easier to read. With the newline support
in string interpolations, your code is easier to read .
POINT TO REMEMBER
Discards are used for unassigned values, and they act as placeholders. In C#
7.0 onward, they are supported. But you must note that _ is also a valid
identifier. Outside of the supported context, _ can be treated as a valid
variable. If you have an identifier named _ already in scope, the use of _ as a
stand-alone discard can result in unwanted outcomes.
Auto-default Struct
15.P3 Can you compile the following code?
struct Employee
{
public string Name;
public int Id;
public Employee(string name, int id)
{
Name = name;
}
public override string ToString()
{
string emp = Name + " has ID " + Id;
return emp;
}
}
Answer:
This code produced a compile-time error in 3.P20 (Chapter 3). But the Visual
Studio Preview Version 17.3.0 (Preview 1.1) allows you to compile this code.
Notice that inside the Employee constructor, we did not initialize the ID; still,
this code compiles. But now you’ll see the following warning that is expected:
Test.Show();
interface ITest
{
static abstract void Show();
}
class Test : ITest
{
public static void Show()
{
Console.WriteLine("Implementing a static
abstract
method in an interface.");
}
}
Answer:
If you do not test this code in preview mode, you’ll see the following error:
Once you enable your project file to test the preview features, the code will
compile, and it can produce the following output:
Explanation:
Here is the Microsoft’s recommendation (
https://docs.microsoft.com/en-us/dotnet/csharp/whats-
new/csharp-11#static-abstract-members-in-interfaces ):
It continues as follows:
You can add static abstract members in interfaces to define interfaces that
include overloadable operators, other static members, and static
properties. The primary scenario for this feature is to use mathematical
operators in generic types.
Author’s Note: In 6.P27, you saw a similar program. I added this program
here to discuss these preview features together .
Console.WriteLine("---15.P5---");
public class testingwarningwave
{
// Some Code
}
Answer:
This code can compile, but you may see the following warning message:
To see this warning, you have to enable the warning wave using the
AnalysisLevel element in your project file such as the following:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>_15.P5</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AnalysisLevel>preview</AnalysisLevel>
</PropertyGroup>
</Project>
Instead of seeing it as a warning, if you also include the element
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>, you
will see a compile-time error, as shown in Figure 15-1.
Additional Note:
You can set a value for AnalysisLevel through the UI also; right-click
the project, select Properties, select Code Analysis, and then select .NET
analyzers, as shown in Figure 15-2.
Figure 15-2 Setting the analysis level through the Visual Studio UI
Operator Overloading
15.P6 Predict the output of the following code segment :
Explanation:
This program shows that you can overload a unary operator (++) as well as a
binary operator (+).
15.P7 Can you compile the following code?
class Sample
{
int _flag;
public Sample(int flag)
{
_flag = flag;
}
Answer:
No. You’ll receive the following compile-time error:
CS0448 The return type for ++ or -- operator must
match the parameter type or be derived from the
parameter type
Explanation:
Microsoft says the following (at https://docs.microsoft.com/en-
us/dotnet/csharp/misc/cs0448?
f1url=%3FappId%3Droslyn%26k%3Dk(CS0448) ):
When you override the ++ or -- operators, they must return the same type
as the containing type, or return a type that is derived from the containing
type.
Console.WriteLine("---15.P8---");
// Some code
class Sample
{
int _flag;
public Sample(int flag)
{
_flag = flag;
}
Answer:
No. You’ll receive the following compile-time error:
using Container1;
sample.AboutMe();
Console.WriteLine($"12+7={sample.Add(12, 7)}");
namespace Container1
{
public partial class Sample
{
public void AboutMe()
{
Console.WriteLine("Experimenting with the
partial
class 'Sample'.");
}
}
Answer:
Yes, this code can compile and run successfully. You’ll get the following
output:
Explanation:
You have seen an example of a partial class. You can see that though the
Sample class has two parts, you can use them together.
15.P10 Predict the output of the following code segment:
using Container1;
using Container2;
sample.AboutMe();
Console.WriteLine($"12+7={sample.Add(12, 7)}");
namespace Container1
{
public partial class Sample
{
public void AboutMe()
{
Console.WriteLine("Experimenting with the
partial
class 'Sample'.");
}
}
}
namespace Container2
{
public partial class Sample
{
public int Add(int a, int b)
{
return a + b;
}
}
}
Answer:
No. You’ll receive the following compile-time error:
Partial Methods
15.P11 Predict the output of the following code segment :
using Container1;
Sample sample = new();
sample.SayHello();
namespace Container1
{
public partial class Sample
{
internal partial void SayHello();
}
public partial class Sample
{
internal partial void SayHello()
{
Console.WriteLine("Hello, reader!");
}
}
}
Answer:
This code can compile and run successfully. You’ll get the following output:
Hello, reader!
Explanation:
You have seen an example of using a partial class with a partial method.
15.P12 Can you compile the following code?
Console.WriteLine("---9.P12---");
namespace Container1
{
public class Sample
{
internal partial void SayHello();
}
}
Answer:
No. You’ll receive the following compile-time errors:
Explanation:
These error messages are self-explanatory. In 15.T7, you saw some
restrictions on using a partial method. If required, you can read them again. So, if
you remove the internal modifier, CS8795 will not appear. The following code is
free from both these errors:
Console.WriteLine("---9.P12---");
namespace Container1
{
public partial class Sample
{
// internal partial void SayHello();//
Error:CS8795
// partial void SayHello();// Error:CS0751, if
class is
// not partial
partial void SayHello(); // No error
}
}
Obviously, for this code, you’ll see the warning saying Message IDE0051
Private member 'Sample.SayHello' is unused.
15.P13 Can you compile the following code?
Console.WriteLine("---9.P13---");
namespace Container1
{
public partial class Sample
{
internal partial void ShowNumber();
}
public partial class Sample
{
internal partial int ShowNumber()
{
return 5;
}
}
}
Answer:
No. You’ll receive the following compile-time error:
Additional note:
It should not be a surprise to you that partial methods are seen inside partial
classes (or structs) and the method signatures should match.
15.P14 Can you compile the following code?
Console.WriteLine("---15.P14---");
namespace Container
{
public partial class Sample
{
abstract partial int ShowNumber(); // ERROR
CS0750
}
}
Answer:
No. You’ll receive the following compile-time error:
namespace Container
{
public abstract class Parent
{
public abstract int ShowNumber();
}
public partial class Sample:Parent
{
public override partial int ShowNumber();
}
public partial class Sample
{
public override partial int ShowNumber()
{
return 5;
}
}
}
Answer:
Yes. If you run this code, you will get the following output:
OceanofPDF.com
Appendix A: A Final Note
Congratulations! You have reached the end of the book and can now
program confidently in C#. If you continue to study the program segments,
examples, and Q&A discussions in the book, you will gain even more
clarity about them and continue your success in the programming world.
As I’ve said before, C# is a vast topic, and this book is too small to
cover all the features of C#. Still, in these 15 chapters, you learned,
reviewed, and experimented with lots of C# constructs and features.
What is next? You should not forget the basic principle that learning is a
continuous process. This book helped you learn the fundamental and
commonly used constructs in C# so that you can understand a C# codebase
better and enjoy future learning.
But learning by yourself will not be enough anymore. Feel free to
participate in open forums and join discussion groups to get more clarity on
C# programming. Researching topics on Google, Stack Overflow, Quora,
and similar places can help you a lot.
A Personal Appeal
Over the years, I have seen a general trend in my books. When you like the
book, you send me messages, write nice emails, and motivate me with your
kind words and suggestions. But most of those messages do not reach the
review sections of platforms like Amazon and others. Those pages are often
filled with criticisms.
I understand that these criticisms help me to write better. But it is also
helpful for me to know what you liked about a book. If I get a chance to
update this book, those constructive suggestions can help me a lot with the
new edition.
So, I have a request for you: you can always point out the areas to be
improved, but at the same time, please let me know about the coverage that
you liked. In general, it is always easy to criticize but an artistic view and
open mind are required to discover the true efforts that are associated with
hard work. Thank you and happy coding!
Index
A
Abort() method
Abstract class
Abstraction
Abstract method
Action delegate
Ahead of time (AOT)
Anonymous method
Arrays
creation
definition
fundamentals
jagged
rectangular, creation
reference type
as keyword
Assembly
Auto-default struct
B
Backing store
base keyword
Base class library (BCL)
Boxing
break statement
C
C# 11 feature
Casting
catch blocks
Central processing unit (CPU)
Checked keyword
Common language runtime (CLR)
Compile-time errors
Compile-time polymorphism
Conditional operator
Constants
const keyword
Constructors
optional parameters
uses
Context switching
Continue statement
Contravariance
Covariance
Cross-platform
C# types
Custom comparison method
Custom delegates
Custom exception
D
Deadlock
Decimal data type
Declaration pattern
Default constructor
Default keyword
Default value expression
Default values
Delegates
compatibility
definition
syntaxes
variance
Design patterns
Destructors
Diamond problem
Double data type
Do-while loop
E
Empty interface
Encapsulation
abstraction
indexers
interfaces
private data of class
programming skills
properties
virtual and abstract properties
Enumeration type
base class
casting
definition
fundamentals
uses, C# programming
ErrorTypes
EventHandler delegate
Events
accessors
arbitrary method
interface
passing data
programming
uses
Event subscription, lambda expression
Exception filters
Exception handling
base class
fundamentals
general catch block
keywords
multiple catch blocks
throwing and rethrowing
Exceptions
Explicit interface implementation
Expression
Expression-bodied members
Expression syntax
Extension methods
F
Filtering exceptions
finally block
_flag variable
Flags enumeration
foreach loop
Foreground vs. background threads
Func delegate
G
Garbage collector (GC)
General catch block
Generic delegates
Generic methods
Generics
analyzing static data
characteristics
coding
constraints
contravariance
covariance
default values
inheritance
interface implementation
motivations
overloading and overriding testing
restrictions
runtime errors
GetLowerBound method
GetUpperBound method
GetValues method
goto statement
H
Heap
Hierarchical inheritance
Hybrid inheritance
I
Immutable object
in keyword
Indexers
IndexOf method
Inheritance
in C# programming
generics
method overloading
method signature
objective
private members
programming fundamentals
sealed Keyword
types
init Keyword
Instance methods
Instances
Interface
case studies
events
properties and indexers
Intermediate language (IL)
Invariance
Invocation list
is keyword
IsBackground property
IsNullOrEmpty(…) method
is operator
Iteration statements
J, K
Jagged array
Join() method
Jump statements
Just-in-time (JIT) compiler
L
Lambda expressions
anonymous method
definition
equivalent code
event subscription
local variables
natural type
program demonstration
programming
restrictions
types
Length method
Long-term support channel (LTSC)
M
MakeDouble method
Marker interface
Method hiding
Method overloading
Method overriding
Method signature
Multicast delegates
Multidimensional arrays
Multilevel inheritance
Multiple catch blocks
Multiple inheritance
Multitasking
Multithreaded program
Multithreading
N
Nested classes
.NET Framework
.NET Native
Nondestructive mutation, testing
O
Object initializers
Object-oriented programming (OOP)
Operator
Operator overloading
Optional parameters
out parameter
P
Parameterized constructors
ParameterizedThreadStart delegate
Parameterless constructors
Parent class
Partial class
Partial methods
Polymorphism
C# 11 feature
code
definition
programming skills
Positional notations
Predicate delegate
Preview channels
Preview features
autodefault structs
newlines support in string interpolations
raw string literal
static abstract interface members
Properties
creation
definition
encapsulation
Q
Query syntax
QueueUserWorkItem method
R
Readonly modifier
readonly keyword
Read-only property
Read-write property
Record type
Rectangular array
ReferenceEquals method
Reference types
Restrictions
Run() method
Runtime polymorphism
S
Safe code
sealed keyword
Selection statements
Self-referencing generic type
SetX method
Show() method
ShowStatus() method
ShowVehicle method
Single inheritance
Sleep method
Sort() method
Stack
Statement lambdas
Static abstract interface members
Static class
Static constructor
Static data
Static lambda
string keyword
StringBuilder
String.Join method
Strings
data type
fundamentals
objects, creation
reference type
String.Join() and String.Concat() methods
variable
Structure read-only
Structure types
definition
fundamentals
immutability
limitations
structure read-only
variable
Subclass methods
Switch block
Switch statement
System namespace
System.Array
System.ValueType
T
Ternary operator
this keyword
Threading
cancellation management
coding
context switching
deadlocks
definition
handling parameterized methods
Join() method
lambdas
multithreaded program
synchronization
ThreadPool
Thread pool
class
coding
drawbacks
process
size
Thread safety
Thread signaling
ThreadStart delegate
throw keyword
throw statement
ToString() method
Trim() method
TryParse method
Type conversions
Type inference
Type-safe function pointers
U
Unassigned enum members
Unboxing
UnRelatedMethod
Unsafe code
Unverifiable code
User-defined event accessors
V
Value-type by reference
Value-type by value passing
Value types
Value type variable
Variance in delegates
var keyword
virtual keyword
Virtual and abstract properties
Visibility control
void return types
W, X
while loop
Y, Z
Yield() method
OceanofPDF.com