415 110 22MB
English Pages 819 [789] Year 2023
C# PROGRAMMING & SOFTWARE DEVELOPMENT
- 6 IN 1 -
CODING SYNTAX, EXPRESSIONS, INTERFACES, GENERICS AND APP DEBUGGING BOOK 1 C# CODING SYNTAX C SHARP SOFTWARE DEVELOPMENT FUNDAMENTALS BOOK 2 C# PROGRAMMING BASICS WRITE, RUN, AND DEBUG CONSOLE APPLICATIONS BOOK 3 C# CODING FUNDAMENTALS CONTROL FLOW STATEMENTS AND EXPRESSIONS BOOK 4 C# TYPE CLASS FUNDAMENTALS BUILT-IN DATA TYPES, CLASSES, INTERFACES, AND INHERITANCE BOOK 5 C# PROGRAMMING EXPLICIT INTERFACE IMPLEMENTATION BOOK 6 C# GENERICS PERFORMANCE AND TYPE SAFETY
RICHIE MILLER
Copyright
All rights reserved.
No part of this book may be reproduced in any form or by any electronic, print or mechanical means, including information storage and retrieval systems, without permission in writing from the publisher.
Copyright © 2023 Richie Miller
Disclaimer
Every effort was made to produce this book as truthful as possible, but no warranty is implied. The author shall have neither liability nor responsibility to any person or entity concerning any loss or damages ascending from the information contained in this book. The information in the following pages are broadly considered to be truthful and accurate of facts, and such any negligence, use or misuse of the information in question by the reader will render any resulting actions solely under their purview.
Table of Contents – Book 1 Introduction
Chapter 1 C Sharp Historical Context
Chapter 2 Object-oriented with Functional Features
Chapter 3 How to Explore Managed Execution in C#
Chapter 4 The Common Language Runtime (CLR)
Chapter 5 The .NET Base Class Libraries
Chapter 6 The Constant Evolution of C#
Chapter 7 Top Level Programs
Chapter 8 Expression-bodied Members & Disposable Types
Chapter 9 Relational Patterns
Table of Contents – Book 2 Introduction
Chapter 1 The C# Development Stack
Chapter 2 Exploring C#'s Building Blocks
Chapter 3 Juggling Types
Chapter 4 Static Typing & Built-in Types
Chapter 5 Type Inference & Arrays
Chapter 6 Back to the Base Camp
Chapter 7 Assembling a C# Program
Chapter 8 Importing Namespaces
Chapter 9 Hunting for Bugs with Debugger
Chapter 10 C# and Smart IDEs
Table of Contents – Book 3 Introduction
Chapter 1 How to Setup the Sample Project
Chapter 2 Understanding C# Statements
Chapter 3 How to Write Simple Statements
Chapter 4 How to Explore Statements and Expressions
Chapter 5 How to Make Logical Comparisons with Statements
Chapter 6 How to Explore Selection Statements
Chapter 7 How to Create Branching Selection Statement
Chapter 8 How to Explore Switch Statements
Chapter 9 Understanding Iteration Statements
Chapter 10 How to Explore Different Types of Loops
Chapter 11 Working with While Statements
Chapter 12 How to Explore Additional Program Flows
Chapter 13 How to Enhance Program Flow Using Loops
Chapter 14 Program Flow Considerations with Dynamic Data
Chapter 15 How to Explore Advanced Selection Statement Features
Table of Contents – Book 4
Introduction
Chapter 1 How to Use Built-in C# Data Types
Chapter 2 How to Work with Primitive Types
Chapter 3 C# Expressions
Chapter 4 How to Work with DateTime
Chapter 5 Implicit Typing Fundamentals
Chapter 6 How to Create and Use Strings
Chapter 7 Escaping Text Basics
Chapter 8 Strings are Immutable
Chapter 9 How to Work with Methods
Chapter 10 How to Find the Correct Method
Chapter 11 How to Pass Parameters by Value & Reference
Chapter 12 How to Use out Keyword
Chapter 13 Named Arguments & Optional Parameters
Chapter 14 Value Types & Reference Types
Chapter 15 How to Create Enumerations
Chapter 16 How to Create First Class & Objects
Chapter 17 How to Use Class
Chapter 18 How to Add Properties
Chapter 19 How to Group Classes in Namespaces
Chapter 20 How to Work with null
Chapter 21 Inheritance Fundamentals
Chapter 22 Is-A Relation Basics
Chapter 23 How to Use Interfaces
Table of Contents – Book 5 Introduction
Chapter 1 C# Definitions
Chapter 2 Programming to an Abstraction
Chapter 3 How to Create Interfaces to Add Extensibility
Chapter 4 How to Use CSV & SQL Data Readers
Chapter 5 Dynamic Loading and Unit Testing
Chapter 6 Dynamic Reader Factory
Chapter 7 Unit Testing
Chapter 8 Implementing Explicit Interfaces
Chapter 9 How to Use Explicitly Implemented Members
Chapter 10 How to Change Interfaces & Default Implementation
Chapter 11 How to Add Default Implementation
Chapter 12 How to Call Default Implemented Members
Chapter 13 How to Make Bad Assumptions
Chapter 14 Additional Features & Abstract Classes
Table of Contents – Book 6
Introduction
Chapter 1 How to Implement Stack Class for Doubles
Chapter 2 Building SimpleStack instance
Chapter 3 How to Create Generic Stack Class
Chapter 4 Advantages of Generics
Chapter 5 How to Create .NET Console Application
Chapter 6 How to Implement Generic Class
Chapter 7 How to Work with Class Constraint
Chapter 8 How to Use the New() Constraint
Chapter 9 How to Work with Generic Interfaces
Chapter 10 How to Create Generic Interface
Chapter 11 Covariance Basics
Chapter 12 Contravariance Fundamentals
Chapter 13 How to Work with Interface Inheritance
Chapter 14 How to Create Generic Methods and Delegates
Chapter 15 How to Write Generic Method with Return Value
Chapter 16 Variance with Generic Delegates
Chapter 17 Special Cases with Generics
Conclusion
About Richie Miller
BOOK 1
C#
CODING SYNTAX
C SHARP SOFTWARE DEVELOPMENT FUNDAMENTALS
RICHIE MILLER
Introduction
Maybe you're a developer somewhere on the spectrum from newbie to veteran or maybe you're not a programmer at all, but you do have at least a conversational understanding of basic programming concepts, like branching, loops and functions, and at least a basic idea of what classes are about. Since you're reading this book, I assume you're curious to know more about C#, but not necessarily because you need to become a C# practitioner. I'm also going to assume you're skeptical in the most complimentary sense. Part of what makes developers good developers is a healthy resistance to taking bold statements about a programming language's benefits at face value. So I will avoid making bold statements about C#'s benefits without illustrating the point or showing you how you can convince yourself. Lastly, I assume you either aspire to be a C# developer or perhaps just someone who can communicate more effectively with C# developers. And so at this point, I just want to understand the essence of what makes C# tick as opposed to spending a lot of time in the details. With all of that in mind, this book is less about the syntax of C# and more about the reasons why bothering to learn its syntax might be useful to you. The goal with this book is to kick start your discovery of C# by taking the following approach. There is a lot of surface area to C#, so I'm going to stick to the ABCs and keep things at a very elemental level. This means I'm going to take some liberties in order to keep things moving and stay at
the big picture level. My goal is to convey the flavor of C#, not provide screenfuls of code that you can execute on your own machine. And because I'm a skeptic who believes you deserve to have big claims about C# backed up by evidence, I'm going to pull back the covers somewhat and reveal just how some of C#'s most powerful features are implemented. And if you who are aspiring C# developers, I hope to help you come away with an appreciation for how C# has handled the inevitable evolution of a language over time. If you do want to experiment with anything you see in this book, these are the tools that were used at the time of writing. I'm using the latest version of the C# compiler that is currently in general release. There may be a preview of the next version of C# available, but I'm not using that for this book. This is the underlying runtime and accompanying set of base class libraries that complements the compiler I'm using. And as for the integrated development environment, I'm using the free version of Visual Studio that anyone can download and install. If you download and install this version of Visual Studio, you will get all three of these things installed and ready to use out of the box, and we'll be able to replicate everything you see in this book.
Chapter 1 C Sharp Historical Context
We'll start by taking a look at the essence of C#, those general features and characteristics that summarize its nature as a programming language, and give you a sense of the experience you'll have programming with it. As is so often the case, historical context has a lot to do with why something is the way it is now. C# is no different in this regard. Before its official debut in 2001, the two most prevalent programming languages in the world were C++ and Java. C++ had been around longer and was appreciated for its ability to support programming while still retaining the very fast performance that characterized its predecessor, C. That said, using it to build apps that ran on more than one platform wasn't the easiest to pull off, and its syntax seemed to be increasingly complex as the language evolved. Java, on the other hand, had acquired quite a large following in a relatively shorter period of time. Although generating fast code wasn't one of its strengths back then, it was appreciated for being much more portable across platforms and arguably more productive for building complex yet still maintainable systems. It was against this backdrop that the designers of C# endeavored to create a language that would embrace the best characteristics of C++ and Java, while simultaneously improving on both. One of the first things the C# designers did was ensure that it was syntactically approachable to C++ and Java developers. This meant that, like C++ and Java, C# would be in the C family of languages, which meant things like using semicolons rather than newlines as statement terminators, using curly braces to group blocks of code comprised of
more than one statement that should be considered to be in the same scope, supporting constructs such as namespaces, classes, methods, and properties. Additionally, indexing is used when working with arrays. And just as with C++ and Java, C# is a strongly typed or, if you prefer, a statically typed language. So in C#, the type of things, like method return values, method arguments, and local variables must be declared in code. While fans of loosely typed languages find strongly typed language is somewhat cumbersome to work with, the effort pays off with the prevention of errors at both compile time and runtime, which is a significant benefit and one we'll revisit shortly. That said, the C# compiler is very often able to infer the type of a variable or parameter by analyzing the context of its usage. In this case, the type of variable n can be unambiguously inferred to be the same as the type of the Length property of the args parameter, which, in this case, is an int. In C#, developers signal that they want the compiler to infer the type of something by using the var keyword instead of specifying a concrete type. So long as there is nothing ambiguous about a type inference expression, the C# compiler will substitute the concrete type, int, in this case, when it compiles your code.
If we open this code in Visual Studio, we can verify that the compiler is correctly inferring the type of n as expected a couple of ways. First, if we hover over args.Length, Visual Studio tells us that property is an int, and if we hover over n, we can see that Visual Studio has correctly concluded that n should be an int as well. Secondly, we can confirm that type information is made available at runtime by inspecting things in the debugger. To do that, I'll run to the point where the for loop begins and then use the debugger's intermediate window to evaluate some of the type information that's available at runtime. For example, calling GetType() on the length property of the args array and then evaluating its FullName property confirms that args.length is technically an instance of the standard System.Int32 type.
Doing the same for the loop variable n confirms that it's also an instance of the System.Int32 type. In this program, the coding convenience we just achieved is literally a wash, since in int and var both involve typing exactly three letters. But type inference pays much bigger dividends when working with custom types, especially when working with something called generics. You'll also see more examples of type inference in just a moment and throughout the book. The fact that this kind of type information is available at runtime, not just compile time, is what makes the next key feature of C# possible. The fact that C# is strongly typed sets the stage for what I would argue is one of the biggest features of C#, which is that it exhibits all of the resilience to change and runtime safety that comes with more loosely typed dynamic languages with the
performance characteristics of statically typed natively, compiled code. That parenthetical remark is one of those bold claims that I mentioned earlier, which should cause you're skeptic antenna to twitch. But the proof is someone involved, so I'm going to leverage a bit of foreshadowing and park the native performance statement on the back burner for now, and focus here on the safety and resilience characteristics. Consider this slight variation of our rudimentary C# program. All this program does is declare an array of integers populated with the numbers 1 through 5, declare a variable named sum that is initialized to the integer value of 0, and then loop over that array of numbers, adding the contents of each element of the array to our sum variable.
Notice that we're using the Length property of the numbers variable to determine how many times our for loop should iterate. This is one of the productivity and safety features that most strongly typed natively compiled languages don't provide. Once the loop concludes, the tally we've accumulated in the sum variable is displayed to the
console. Let's switch over to Visual Studio and use this to explore one example of what I mean by safe. I've switched to Visual Studio and have a simple C# project ready that reproduces the code shown on the previous screen. As expected, if I build and run this program, the output is 15. Likewise, if I change the loop such that it only iterates four times as a value, the output is 10, as expected.
But what happens if I now change how I initialize the numbers variable so that it only has three values in it, like so. If you are coming from a C++ background, you would probably say something like the results are undefined or it will crash, because the C++ compiler would have generated machine code to sum up the four consecutive integer values that appear in memory, starting at the location occupied by the numbers variable. But if you are coming from a Java background, you would probably guess, or at least hope, that this kind of error results in an exception of some sort, indicating that your program has attempted to access the given array at an location. Thankfully, C# works like Java in this regard, and an index exception occurs. This is actually a good thing. In the case of this simple program, this kind of runtime type safety prevents incorrect output, but had we been writing into our array and using a native language like C++, an error like this one would result in memory corruption. If you're a lucky C++ programmer, that corruption would still result in an exception, but if you're unlucky, which is more typical, that corruption would go undetected for potentially quite a long time, resulting in undefined program behavior that can be exceedingly difficult to pin down. That's one example of the safety of C#. Now, let's take a look at what I mean by resilience.
To show you what I mean by resilience, I've set up a new application that uses a very simple point structure that's been defined in a separate library. For now, the important bit is that the definition and implementation of the point structure is in a separate binary, which allows it to be reused across multiple applications, including this one. All the application does is initialize a local variable called pt, set its X and Y fields to initial values, and then display the result of calling the point structures toString method to the console.
Notice that when this version of the program is run, the implementation of toString returns a string that shows the X, Y
coordinates of the point in standard parentheses notation. Now let's use this simple setup to replicate a very common occurrence in software systems, which is that a library we are using is updated to a newer version, but deployed to systems that still have the original version of the calling application in use. In this demo, deployment just means copying a version of the point library into the folder where the main application executable resides. We'll replicate that scenario by changing our point into a point by adding a new field that will hold the Z coordinate for the point. We're going to rebuild just the library, and then "deploy" the updated library to the location where our app is installed. If we rerun our application, which we have not changed or recompiled to be aware of the new field, the application still runs as expected, albeit with a Z field of 0. This demonstrates C# code's resilience to change. When the application developer is ready to opt in to taking advantage of the new functionality available in the library they are using, they can of course rebuild and redeploy their application and now have access to the extended functionality.
This ability to maintain resilience in the presence of changes to dependencies is a hallmark of C# and other managed languages. And to revisit the previous point about safety, should a library developer make some sort of breaking change to a type definition, which I'll
demonstrate here by changing the type of the fields in our point structure from integers to strings, an exception would result, since the expectations encoded in the dependent application about the nature of the point structure can no longer be guaranteed by the runtime. If you're a skeptic, or merely observant, you'll have noticed I made a claim about resilient and safe with native performance.
We'll explore exactly how such safety and resilience are pulled off and revisit the native performance post in the next chapter on managed execution.
Chapter 2 Object-oriented with Functional Features
For now, let's turn the corner on our initial discovery tour and touch on C# support for programming. Consider again our simplistic array of numbers program. To demonstrate C#'s inherent I'm going to forego defining some sort of base class and derived class hierarchy.
Instead, I'll simply point out that at some point every type in C# derives from or extends a standard .NET type named system.object, which, among other things, provides a method named get type that you can use at runtime to ask any object for information about its type. This is done by calling the get type method, which we leveraged earlier. Having done that, we can display the name of the object to the console by accessing its full name property, which in this case, turns out to be a type called system.int32 with array brackets after it. This denotes it's an array of signed integers. This is an example of a C# language keyword or construct being mapped to a standard type in the .NET base class library, which is another way that C# endeavors to be approachable to programmers familiar with other programming languages.
There are numerous such language mappings in C#. We can also go one step further. As it turns out, not only can we use our type reference to determine the name of this type, we can also use the base type property of any object to determine whether that type derives from or extends some other type. And if we do this in a loop like so, we can walk the entire inheritance tree until we reach the root of our numbers arrays type hierarchy, which when run, would produce this output. For now, let's examine what I mean by with functional features.
Let's return to the original version of our array of numbers program, which computed and then displayed the sum of the integers within
the array. Our original implementation used a standard for loop to iterate over each element of the array adding the current array elements value to the running tally being stored in the sum variable. While this is a perfectly functional way to implement this algorithm, pun intended, C# also allows developers to embrace, if they choose, a more functional programming paradigm.
In functional programming languages, executable expressions or functions are first class entities within the syntax of the language and can be treated like any other typed object, including being referenced by variables and passed into and out of other functions as parameters and returned values. In C#, the functional equivalent of the previous implementation would look like this. Instead of writing a traditional for loop, we would instead simply invoke an Aggregate method on our array of numbers.
The Aggregate method takes two parameters as input. The first parameter is simply the initial value of our accumulator, which in our case is just 0. The second parameter is an expression of the function we would like the Aggregate method to invoke for each element in the numbers array. If you look up the documentation for the Aggregate method, you'll find that there are three elements to this expression, a parameter list expression, followed by a fat arrow operator, and then the set of statements we would like to execute for each element in the input array. Together this line of code forms what C# refers to as an expression lambda. The documentation for the Aggregate method indicates that it will call our functional expression with two input arguments, the first being the current value of the accumulator and the second being the value of the current element in the input array. While the order of those parameters is defined by the Aggregate function, we can name those parameters any name we like, in this case, total and num are apt names for
those two arguments. The body of our anonymous method then simply returns the result of adding the current value to the running total. Since our anonymous method consists of just a single statement, we don't need to actually use the return keyword, it's implied in this case. And since this syntax is really just shorthand for defining an anonymous method that contains the required parameter list and a method body that contains whatever it is you want to execute, should you need to do something involving multiple statements, you can use a standard pair of curly braces to denote the collection of statements you would like to comprise that anonymous method. Note that when using this variation, which is officially referred to as a statement lambda, the return keyword is required. This is because the code within the curly braces can be arbitrarily complex, including the use of multiple return statements, so the compiler will no longer assume which statement should result in the return value for the functional expression.
The Aggregate method shown here is actually defined in another namespace called system.linq where link is actually an acronym that stands for language integrated query. What we've looked at so far is
just the very top most tip of the proverbial iceberg. Although I'm sticking to the ABCs of the language here and just using rudimentary console applications to demonstrate things, C# is much, much larger and more capable than what you see here. But since 2014, C# has been available as an open source project and supports the development of console apps and web apps that target Windows, macOS, and Linux. C# is also quite general purpose and can be used to build everything from desktop apps to mobile apps for Android and iOS, to web apps and even games and automation plugins from Microsoft's PowerShell technology. In summary C# is fairly approachable, although mostly for developers with a C++ or Java background, and that was by design given the context of its birth. And like C++ and Java, C# is strongly or statically typed, which helps catch programming errors earlier and ensure program correctness. And the combination of its strong typing and runtime type safety means that C# is very resilient to change and yet safe when coding errors or breaking changes are introduced. Although C# has its roots as an language and fully supports classic design, it has evolved to support more functional approaches to programming as well, all of which is being done as an project and which can be used to meet a variety of application development needs.
Chapter 3 How to Explore Managed Execution in C#
Before I make a good on my promise to explain the native performance claim I made earlier, I need to zoom out a bit and explain what the managed execution portion of this module title is referring to, which entails a tour of how the source code you type into an editor, or IDE, gets transformed into something a computer or device CPU can actually execute. Languages like C++ are usually referred to as compiled languages. Compiled languages tend to be strongly typed, but because that type information is generally only used in the compilation process, type safety is enforced at compile time. And there is a lack of type information available at runtime, although modern C++ has made a valiant effort to surface information. And notably, compiled languages require the developer to manually manage memory usage. When using a language like C++, development starts with the use of an editor or ID to create text files that contain C++ source code. Those source files are then fed into a program called a compiler, which converts the source code text into binary code that is appropriate for a given platform on which the developer intends to run their program. Because the contents of such executable files are native to a given process or architecture, the operating
system on the target machine can essentially feed the contents of a native executable image directly to the CPU, which the CPU then executes. If the application has a function that is called 1,000 times, the CPU simply executes the binary encoding of that method 1,000 times. Absolutely no time is spent preparing that code to execute, as the source code to machine code translation was handled by a compiler long before that application was deployed and run. That said, there are several downsides to this type of architecture, including a lack of resilience to change that we looked at in the previous module, the lack of portability and need to build one's code using multiple compilers in order to generate versions of the app that can run on different platforms, and the requirement that the programmer manually manage the allocation and deallocation of memory and other resources used throughout the life of the program, which is, to put it mildly, an endeavor. In keeping with the theme of this book, I'm simplifying the process quite a bit here, but hopefully that gives you a taste for how code comes to be executed when using a compiled language. In contrast, interpreted or dynamic languages lie at the very opposite end of the code execution model spectrum. Interpreted languages tend to be very loosely typed, ranging from a complete absence of type to languages where an entity's type is determined at runtime and can change over time as the program runs. And while the burden of memory management is alleviated for the programmer and things like portability and productivity tend to be great, performance is usually the most noticeable tradeoff with interpreted languages with bugs due to type permissiveness being a close second. This is because although the development process starts the same way using an editor or IDE to create text files containing something like Python source code,
everything else involved in translating source code into instructions happens within an interpreter, which is a program that runs on the target machine, and generally speaking, converts language statements in the machine code on a basis. So if the application has a method that is called 1,000 times, the interpreter's translation of source code to machine code for that method may be happening 1,000 times. There are interpreters that are smarter than that and seek to detect when they can translate source code to machine code more efficiently. But the design of the language itself, in terms of features and syntax, tends to limit the efficiency gains that can be achieved. And it is this type of architecture that allows interactive coding experiences like Python's REPL, or loop, and portable Jupyter Notebooks for processing goodness. Which brings us to a somewhat hybrid system that I'm referring to in the module title as managed languages. Managed languages such as C# tend to be strongly typed, similar to their compiled language cousins. But unlike their compiled cousins, managed languages make all of the type information that is available to developers while they are coding available at runtime. Such runtime availability of type information allows for far more robust safety checks, the convenience of automatic memory management via something like a garbage collector, which is more akin to an interpreted language experience while also making it possible to achieve much faster performance profiles. With managed languages, developers run their code through a compiler similar to the compiled language system. However, managed compilers do not produce machine code that can be processed directly by machines to CPU. Instead, managed compilers produce a binary file that contains an intermediate representation of the source code, which, in the case of C#, is called Intermediate Language,
or just IL. Java compilers work similarly, except that their output is referred to as byte code. In C#'s case, the file produced by the compiler is called an assembly, and it contains a complete, lossless binary encoding of the developers' source code. Because .NET assemblies do not contain native machine code, they cannot be executed directly by the CPU. So in order to be executed, an execution engine of some sort must be present on that target machine, which converts that IL into CPU understandable machine code on a or JIT, basis before passing that code to the CPU for execution. The key part of this architecture is the nature of this system. The machine code generation step is only done the very first time a given method is invoked. The generated code is then cached and used for all subsequent invocations. But the entire design of managed language systems is that the language, the intermediate language binary representation, and the execution engine itself are all designed from the beginning to support the efficient generation of machine code if and only if that code ever needs to be executed, hence the terminology. So if a given method is never called, no time or resources are spent generating the machine equivalent of its intermediate language representation. But if a given method is called, the translation of that method's IL into machine code is triggered within the execution engine, which then removes itself from the call chain for all subsequent invocations of that method. In other words, if that function is called 1,000 times, the translation of IL to machine code occurs once, and then the other 999 invocations are carried out with no further involvement of the execution engine. And it is the JIT compiler that also assists with runtime type safety since part of the IL to machine code translation process involves verifying the type safety of the function that is being compiled and encoding
other safety checks such as array access attempts. This is what makes it possible for managed languages to exhibit the performance characteristics of compiled languages while still providing the convenience of automatic memory management, safety, and resilience to change that we looked at earlier.
Chapter 4 The Common Language Runtime (CLR)
For .NET applications, including but not limited to those written and C#, the execution engine is called the Common Language Runtime, or CLR. This is because ILE is the common language that all .NET compilers emit, which means all managed languages such as C# and others share a common type system. There are actually several variations of the CLR spanning over two decades of .NET evolution and supporting various hardware platforms and device operating systems. The two most common versions of the CLR today are the most recent version of what is simply referred to as .NET, which is the version of the CLR we are using in this book, and the legacy version of what is referred to as the .NET Framework, which is spelled with a capital F and is specific to the Windows operating system and therefore not In general, all new application development should target the version of .NET in order to maximize your application's reach. Apps and libraries that target the version of the .NET CLR will still run on Windows, which is what I've been using in this book, but they will also run on macOS and several flavors of Linux. To a skeptic like me, the resilient and safe, but with native performance claim, comes across as the waviest of hand waving. That's a big claim. If you are also a skeptic or if you just like seeing how things are implemented, this next demo takes a look under the covers of a CLR using some specialized tools and shows you just how this JIT compilation magic is performed. But I should warn you, you will see things like disassembled machine code and references to registers and call stacks, which is somewhat of a departure from the
That said, it's okay if you've never seen any of those things before. The point of this demonstration is not to acquire an understanding of those things, but to prove the claim that C# can be both safe and resilient to change as a result of being a managed language and yet still exhibit the performance characteristics of native code. However, if you are content to take my word for it or don't care to know how exactly the CLR pulls off JIT compilation magic, feel free to skip. You can rest assured that you will not miss any of the big picture regarding C#, only a bit of proof that one of the biggest claims made in this book and elsewhere is, in fact, true. For the adventurous among you, let's dig in. To show you compilation in action, I've created a very simple C# application that adds two numbers together and displays the result to the console.
However, to give us a chance to observe the before and after aspects of compilation, I'm calling a separate method named Add to perform that arithmetic. Similarly, I've strategically placed calls to console.readline both before and after the call to that Add method in order to pause the application and give me a chance to attach a
specialized debugger that we can use to observe compilation in action. I'll build the application, but won't run it from here. Instead, I'll switch to the debugger where the remainder of this investigation takes place. In order to show you compilation, I've switched to a different debugger from Microsoft called WinDbg. This debugger is not included with Visual Studio, but you can download and install it for free. To get started, I'll launch the demo application I just built which is called jitviawindbg.exe.
When WinDbg launches a program, it pauses execution very early in the launch sequence in order to give you a chance to set breakpoints or perform other prep. We don't need to do anything yet so I'll just use the g command to tell WinDbg to keep going. Notice that our application has displayed its initial message to the console and is now blocked in a call to console.readline waiting for us to press Enter before calling the Add method.
We'll use this moment to have a look around at the state of our application before JIT compilation is triggered for that method. I'm using WinDbg for this demonstration because it ships with a very special debugger extension that is developed by the same team that builds the CLR, which means it knows the location and shape of the CLR's internal data structures. This debugger extension is called SOS, which stands for Son of Strike. I'll spare you the history lesson on how it got its name, but you'll see me entering SOS to access its specialized commands so I wanted to mention that. For example, if I enter !sos.help, the SOS extension will display all of the commands it makes available to us within the WinDbg environment.
I'll use that syntax of an exclamation mark followed by SOS followed by a period to access several of these commands. For example, executing !sos.ee version will display version information about the .NET runtime or execution engine that this program is using.
Recall that our application is currently paused on the first call to console.readline within our main entry point method which occurs before the very first call to add. If is true, I should be able to find evidence that the Add method has not yet been compiled, I can do that in two steps. First, I'll use the name2ee command to look up the Add function. The first parameter to this command is the name of a .NET assembly and the second argument is the name of a type entity defined within that assembly. In our case, the assembly is
named jitviawindbg and the entity I want to look up is the Add method of the Program class. Note that this command confirms that our Add method has not yet been converted from IL into machine code.
Additionally, if I attempt to use the SOS command to unassemble or inspect the machine code for the Add method, it will also tell me that the add method has not yet been jitted, so it cannot convert the machine code for that method back into it's more readable assembly language view. However, if JIT compilation is working the way I said it does, we should expect that the next time we use the same command, which will be after the call to add has occurred and theoretically triggered compilation, that we will see something different. Similarly, if I unassemble our main method, we can see two things. First, main itself has already been compiled, which makes sense since we're currently paused within main on its first call to console.readline. And second, we can see the call to our Add method from within main.
Don't worry if you're not familiar with reading assembly language, which is what we're seeing here. WinDbg is kind enough to show us that this call instruction corresponds to a call to the Add method of the Program class, and these two instructions here represent the values 30 and 12 being passed as parameters to add.
We can use the question mark operator in WinDbg to evaluate these hexadecimal expressions to confirm that. And if we use the unassemble command again to inspect the target of that call to the Add method, we find that we are actually calling a function named PrecodeFixupThunk, which is the function that actually triggers the
compilation step. Now that we've confirmed that JIT compilation of the Add method has not yet been triggered, I'll use SOS to set a breakpoint at the start of the Add method.
This is done using the bpmd command which sets a breakpoint on a method based on its method descriptor address, which is the data structure I referenced in the unassemble command as well. This breakpoint will be triggered when our main entry point calls the Add method after the CLR has generated the machine code for our Add method, which will give us a chance to look around after the JIT compilation process has happened. Having done that, we are ready to let our application run full speed until that breakpoint has been hit. With the application running again, we can now press Enter in our console which will result in main proceeding with its call to add, which should trigger the breakpoint we just set. At this point, WinDbg has indicated we've triggered that breakpoint, which means compilation for the Add method has now occurred. We can verify this by repeating our unassemble command from earlier to see the assembly language view of the machine code that was generated by the CLR. There is a lot going on here, but don't worry about the details. The key bit is that these three lines of code are the instructions generated by the CLR's compiler for adding the two
parameters passed into the Add method to one another and then returning the sum to the caller. And if we revisit the call to add from within main where we previously saw a call to a mysteriously named function called PrecodeFixupThunk, we now see something different. Instead of a call to PrecodeFixupThunk, which triggers compilation, we now see that main's call to add goes directly to the start of the Add method's machine code. This confirms that the CLR has now removed itself from the call sequence between main and add, which means that all subsequent invocations of the add method from anywhere in the application, not just main, run with no CLR induced overhead. It is this architectural design of having the C# compiler generate IL, which the CLR then compiles to machine code on a basis that results in all of the type safety and resilience of managed execution while retaining the performance characteristics of natively compiled code.
Chapter 5 The .NET Base Class Libraries
The last piece of the puzzle regarding managed execution in C# is the role of libraries, specifically the standard set of libraries that are included as part of a .NET installation. Whereas the CLR provides the runtime critical services, such as JIT compilation and garbage collection that are essential to the proper execution of all .NET applications, other code that is more utilitarian and commonly, but not always required, exists in a suite of libraries that are part of the .NET installation on a given machine and that application and library developers can take advantage of as needed. These libraries are called the base class libraries and provide classes for working with text, performing file AO, and numerous collection classes such as lists, queues, and hash tables, and many other types. Together, the CLR and the BCL provide the foundation for apps built using any .NET language, not just C#. Chances are, if you need a bit of utilitarian code or even an entire application framework, it is provided for by one or more types in the BCL. The implications of what I just said highlight a bonus big picture aspect of what we've seen so far, which is that because all managed .NET code shares a common language in the form of their intermediate language representations, any investment you make mastering the ins and outs of using the class libraries, whether for building console apps, web apps, or mobile apps is an investment in skill development that transcends C# because a side effect of learning C# is that you learn the broader .NET platform. This also means you will be able to choose the right language tool for the job. Gone are the days of making an
all or nothing decision to choose the language that a large application or suite of applications will be built in. If C# is the best tool for a given job or the language that a given team is most proficient with, then that's the tool you can use. On the other hand, if VB or F# is the best tool for a given job or the language a different team prefers, then the majority of your skills transfer because although there are syntactic differences from language to language that you would need to understand, any investment you've made becoming proficient with the various classes and application frameworks defined in the BCL is transferable. Now, I'll demonstrate this kind of common language capability in action by writing a C# application that leverages a mathematical library written in F# using the same BCL types to communicate. As with the previous demonstration, I have created a very simple C# application that calls an Add method to compute the sum of two integer values and then display the result to the console.
What's different is that I've implemented this Add method in a separate library named calc, which I've implemented using F# rather than C# because F# is a functional language particularly for mathematical problem domains.
Even if you've never seen F# before, it's possible to recognize what's going on here. The type statement here defines a class named Calculator, which is itself defined within a namespace called fscalc. The equal sign here indicates that this class is comprised of the following members, the first of which is a static member named Add, which takes two floating point parameters named a and b and then returns the result of evaluating an expression a + b. This means that the Add member is actually a method and its return value is the sum of a and b. The three additional methods named Subtract, Multiply, and Divide are then similarly defined. Building and running our program confirms that the interplay between the C# application and the F# library works as expected, and we can even use the Visual Studio debugger to single step directly from the C# application into the F# library and back again. This is made possible because although the C# application and F# library are written in different languages, both share a common type system by means of the common intermediate language that both compilers generate and that the CLR knows how to work with. The fact that managed languages in .NET share a common type system in this way is what makes your investment in learning C# a transferable investment in the broader .NET platform which allows you to choose the right tool for the job. In summary, you've seen that the C# compiler, as well
as all other managed .NET language compilers generate intermediate language, which is the common language of .NET and which the CLR converts into code on a basis at runtime. And since the generated machine code is cached and reused for all subsequent invocations, it exhibits native performance characteristics subsequent to the JIT compilation step. It's that JIT compilation process that enforces the runtime type safety of your code, ensuring the integrity of the application when it executes. And supporting your code are the base class libraries which provide a combination of basic types and sophisticated application frameworks. Now that we've taken a look at the key features of the C# language and its relationship to the common language runtime and base class libraries, I want to give you a sense of what you can expect to see moving forward as the language continues to evolve and improve.
Chapter 6 The Constant Evolution of C#
As a developer or aspiring developer, one of the realities you face is dealing with constant change. Hot new technologies appear almost daily, some become widely adopted, all except the really bad ideas that are dead on arrival evolve over time and then eventually they fade into obscurity to be replaced by the next new hotness. As a technology, C# is no different. It debuted in general release in 2001, has undergone constant change since then, and will undoubtedly undergo more change in the future. I am not going to provide an exhaustive history lesson on the evolution of C#, but what I do want to do is show you some select examples of changes that C# has undergone over the years so that you can get a sense of what to expect moving forward should you decide to invest your time learning more about C# because while I don't know exactly how C# will look five years from now, I am confident that it will look different, yet familiar to how it looks today. Contrary to what every stockbroker will tell you, the past actually is a pretty good indicator of the future, at least as far as technology evolution goes. So before I round out this aspect of the C# by showing you some specific examples of how C# has evolved, I wanted to share a few of the truisms that I've observed over the last 20+ years of C#'s evolution so that you can have a sense for what to expect moving forward and hopefully take comfort in knowing how C#'s future evolution is likely to play out. First, changes tend to be incremental or evolutionary rather than
disjointed. And as those new features become available, you can choose whether and when to adopt them in your code, which means that new language features tend to be and not disruptive. Commonly, changes stem from the current idioms and best practices adopted by the broader community of C# developers, appearing as language features in future versions of the tools. And finally, if and when you do adopt new features, the result will generally be code that is simpler, faster, safer, or possibly all of those things. For these reasons, it may have been more appropriate for me to name this module The Conventional Evolution of C# because the organically formulated conventions of the broader C# community play an instrumental role in how the language of C# changes over time.
Chapter 7 Top Level Programs
With those principles of C# evolution in mind, let's take a look at a few examples from C#'s history, up to and including present day. We'll start with the canonical first application developers commonly create when learning a new language. In this case, we essentially have a C# application that displays Hello, world to the console in a grammatically and historically correct manner, except that it's actually a program.
This is because C# does not support the notion of global functions. Instead, functions always exist as methods on a type. So the simplest form of C# application requires a type, the Program class in this case, although you can name the enclosing type anything you like, in which to house a method, which, in the case of an application main entry point, is required to be a static method called Main. As long as the compiler only finds one method among all of an application's source files matching the requisite shape for an entry point method, it will select that for use as the main entry point. And although I could technically collapse this code to make it occupy only one line by removing the new lines, doing so would not make the point I'm trying to
make. While one could argue that this particular language feature was long overdue, C# supports, starting with version 9, what are referred to as statements, which can be used to implement a program with just one actual line of code. So as long as there is only one source file included in your project that leverages statements, the compiler will happily produce the enclosing type, and everything in your source file will simply be included within the main entry point that the compiler generates for you. That said, the original style of writing a main entry point as part of a class is still supported. And if your program accepts arguments like this variation does, you can simply assume the existence of a variable named args and reference it like you normally would in order to access any arguments passed to your program when it was started.
The C# compiler simply declares it behind the scenes as if you'd done so manually. However, I've also slipped in one additional change, which is that I have now leveraged a feature of C# that was actually introduced a few years ago in version 6 called string interpolation. An interpolated string can be recognized by the dollar sign preceding a string literal and executable expressions embedded within curly braces inside that string. When interpolated strings are encountered, the C# compiler simply generates code that performs the operation within the embedded curly braces and
then uses the resulting value of those statements to compose the string, which, in this case, is then passed to Console.WriteLine. While the string interpolation feature is not new, it's another example of a language evolution feature that can be opted into at your convenience. Switching gears a bit, here's an example of an exceedingly simple Point class. This class is similar to the point structure that was shown in an earlier, except that we are using a class here with two private fields called _x and _y to hold the x and y coordinates for the point and two public properties called X and Y that allow callers to read and write those values.
This is a contrived example to keep things simple. But in practice, this is commonly done in order to do parameter validation on the value that is being passed in to the property setter in order to
maintain the integrity internally of an object. The point is that this approach quickly became a recognized best practice for designing types. And so the C# language designers took steps to facilitate this type of design by implementing some syntactic enhancements. This is how that it became possible to implement that pattern, beginning with C# version 3.
This version of the Point class leverages auto properties, which means we don't need to explicitly define private backing fields to hold the values we want to store. Instead, we can simply declare our publicly accessible properties and let the compiler handle that behind the scenes, nor do we need to implement the canonical body of the getter and setter for each property. The compiler will implement those too in the manner that you would expect, although without parameter validation. The result is definitely a lot cleaner than before, and you can always use the original syntax if this doesn't suit your specific needs. Much more recently, C# 9 codified support for a common pattern that involved the desire to define a property that has its value initially set by the caller when an instance of the type is initially constructed, but that could only be read by outside callers from that point on. A property that can be initialized once, but then only accessed in a manner subsequently by callers outside of the type itself, is implemented by using the init keyword instead of the set keyword to define the setter. Init properties like this remain read/writable from within the
other members of the point type itself, just not from the outside. While we're exploring the intricacies of Point class design, I'll show you one last enhancement we might make to this class definition.
Chapter 8 Expression-bodied Members & Disposable Types
Beginning with C# 6, it became possible to take advantage of a language feature called members to define the body of a type's members.
In this case, I'm leveraging this feature to eliminate some of the structural syntax associated with implementing the virtual ToString method. This is only possible here because the implementation of ToString consists of just a single expression. But whenever that's the case, which is fairly common, members allow the structural syntax related to defining a single expression member body to be omitted and replaced with the simple fat arrow syntax. The next evolutionary example I want to share is a great example of another way that C#'s approach evolution tends to support adoption of new features on an basis. However, this example does assume you are at least generally aware of how something called exception handling tends to work in most modern programming languages. It also alludes to some of the complexity that comes with writing managed code in a garbage collecting execution engine that interacts with native operating system resources that are not being managed by a garbage collector. If neither of those situations are familiar to you, just let this example
gently wash over you and trust that when the time comes, you'll come to appreciate how this feature works. With that disclaimer out of the way, consider the Widget class shown below.
What this class does is not important for this discussion, hence the vague class name and the even more vague DoSomething method. The key point is that the designer of this class intends for callers to do two things in order to properly use the Widget class. The first requirement is that users call the Cleanup method when they are done using the object. This type of thing is almost never required for strictly managed code. But if the Widget class were implemented such that it leveraged underlying resources provided by the device operating system directly, this is very important. The second requirement is that users take steps to ensure that they call the Cleanup method, even in the face of an unexpected exception, while the Widget instance is in use.
This can be accomplished by leveraging C#'s construct. Should an exception occur within the body of the try block, the CLR will ensure that the body of the finally block will still be executed. While this version of the code is now robust and will properly meet the usage requirements of the Widget class designer, being a good widget customer has become tedious. The evolutionary improvement to this pattern was twofold First, the widget developer can formally advertise its desired usage by implementing a standard BCL interface called iDisposable. The iDisposable interface defines just a single parameterless method called Dispose, and that has a void return value. So we've renamed the original Cleanup method on the Widget class to Dispose, which satisfies the only requirement of implementing the iDisposable interface.
Second, widget users can now signal their desire to have the compiler automatically generate the requisite exception handling construct that was shown a moment ago by leveraging the using construct shown here.
To leverage this construct, the disposable type that was previously instantiated just above the try block is relocated to within the parentheses, and the code that was previously enclosed within the try block is simply located within the curly braces of this new using construct.
While some might argue that the choice of the overloaded meaning of the using keyword in this context is confusing, since it's completely unrelated to the use of the using directive as a convenience, others have argued that the choice of using here is apt since this syntax essentially codifies the user's sentiment that. As for myself, I will leave my own 2 cents on this topic firmly in my pocket. However, this does illustrate another common aspect of C#'s new feature strategy, which is to enable your own custom types to plug into and leverage new language features by means of implementing a interface, iDisposable in this case. But there are numerous other examples where this approach has been taken to great effect. Examples include a variation of the for loop called foreach and a massively amazing feature of C# called Language INtegrated Query or LINQ.
Chapter 9 Relational Patterns
For the record, forcing you to squint is part of the point I'm making here. What we have on the left is a Person class that defines a property named Generation, which returns an enumerated type, also called Generation.
The Generation enumeration represents each of the generational cohort nicknames as they are currently defined in the US. Since generational cohorts are determined based on someone's birth year, not their age, the implementation of the Generation property performs a series of simple tests to identify the range within which someone's birth year would result in them being labeled with each of the defined nicknames.
For example, individuals born from 1946 through 1964 are referred to as Baby Boomers. Individuals born from 1965 through 1980 are referred to as members of Gen X and so on through the generation born from 2013 to now, which is called Gen A. Implementing this type of relational pattern in this manner is fairly arduous and certainly unpleasant to read. Thankfully, this is how that same logic can be implemented today.
This version of the Generation property starts out by taking advantage of the expression bodied member syntax we looked at earlier. The expression being used here is called a switch expression, which specifies a value to test, followed by the switch keyword, which is a block construct enclosed in a pair of standard curly braces. Within the body of the switch expression, we can simply specify the relational tests that should be applied to that value being tested. Because the switch expression defines the value being tested, we don't need to repeatedly include the value name to test like we did with the standard if/else construct earlier. We are also using a new
conjunctive and logical pattern to test whether multiple tests match. The value to return for a given pattern or a combination of patterns is specified to the right of the fat arrow operator. Lastly, we specify the value to return when none of the relational tests match by using something called the discard operator, which was introduced in C# 7. As with the other evolutionary improvements we've looked at, you aren't required to leverage this new syntax. However, these new features result in code that is much less arduous to write and, in more ways than one, much easier on the eyes, all with the same performance characteristics of the classic approach. While there are many more examples of C#'s approach to feature evolution over the last 2+ decades, I hope that the small sample we've explored gives you a sense for what to expect should you choose to adopt C# as your next programming language. It's fair to say that you can expect to see continuous improvement of the language in a manner that doesn't disrupt your productivity and gives you control over the timing of new feature adoption in your code base resulting in code that is increasingly simple, safe, and in some cases, faster. This brings us to the end of our tour of C#'s key features. I hope you've found this book informative, revealing, and more importantly, helpful for gaining a understanding of the core characteristics of C# and its runtime environment, and perhaps prepared to continue on a learning journey towards personal proficiency with C# armed with a sense for what to expect as C# continues to evolve. I wish you all the best!
BOOK 2
C#
PROGRAMMING BASICS
WRITE, RUN, AND DEBUG CONSOLE APPLICATIONS
RICHIE MILLER
Introduction
Before we start, a quick version check. To prepare this training I used the C# 9 and these versions of Microsoft .NET and Visual Studio. Almost everything we say here is also valid for older versions, C# 8 or earlier, with a few exceptions. We'll come across one or two C# features that are missing from older versions of C#, but I'll let you know when that happens, and there'll be optional features anyway, not essential to this book. What if you're running a later version of C#, 10 or later? Well, even then you should be fine. Again, you might see small differences, especially in things like Visual Studio menus, but chances are you won't have much trouble working around those differences. Most information in this book is fundamental to the way C# works, so I don't think it will get stale anytime soon. However, if you are running C# 10 or later, there is one thing that you'll have to do differently, and I'll tell you about it before the end. This is training for newcomers to C#, and when you start learning a new language from scratch, the first program you want to see is usually Hello World, right? A program that prints something on the screen. In most languages, that's a very short program. It looks something like this maybe, but C# is different.
Are you ready? Here is Hello World in C#.
There is a lot going on here. In recent versions of C#, you can make this program shorter, we'll see about that later, but this is the traditional version of Hello World that works on every version of C#. It's a dozen lines, and most of these lines are boilerplate as they call it, code that exists only to support the lines that actually do something. It is actually only this line that prints on the screen. So, you look at this and you might wonder, if it takes so much code to print something on the screen, then how much code does it take for a real application? Well, I hope that as you learn C#, and even in this same training, you'll get convinced that yes, C# is verbose, but it's not always this verbose. And when it is, it may be for the better. Believe it or not, all this code actually makes the language easier to use in many cases, but I'll make a case for this at the end of this training. For now, let's start delving into this code. How are we going to look at this code? Well, many C# tutorials would say, okay, a basic C# program is complicated, let's ignore most of it and focus
on the part that actually does something and you will understand the rest in due time. But I won't lie. I don't like this idea of understanding half the things and then moving on, so I propose to you another goal for this book, that is let's tackle Hello World head on. Let's understand everything that's going on in a simple program like this, boilerplate and all. This will be your first step into learning C# after all, so we can take this chance to leave no stones unturned before you move on to more complex code. So, plan. First, start from scratch, set up our development environment, and generate a Hello World program. And then we spend the next module wrapping our mind around Hello World, all of it, or nearly all of it. We'll leave one or two details for the following module, but it will be mostly the next module focusing on Hello World. Along the way, you'll get acquainted with the basic building blocks of C#. I think it will be a surprising amount of stuff. Then, as we complete our understanding of Hello World, we'll also start changing that program to do something more useful than, printing on the screen. We'll take another file and change the basic Hello World scaffolding to use functionality from that file, and the result will be a toy program that's actually kind of useful. Nothing major, but still a real program. And once we have that, we'll be able to talk about debugging, an essential practical skill when you're getting started, or actually even when you are an expert. And finally, we'll wrap up this training to look back and get a broader view of the general approach of C#, the philosophy of coding in this language. Most of this training is about looking at code and making sense of it, but that doesn't mean that we're not going to also write a few lines of code or more often edit code. And, of course, if you want to follow along on your own system, please do that. Some people need to touch the examples to understand them. I'm one of those people, so if you're like me, you
might want to run the code on your own computer and maybe experiment on your own.
Chapter 1 The C# Development Stack
Here's a quick laundry list of what we need to write C# code. We need the Microsoft .NET platform. It's available for the major operating systems and it supports multiple languages, and C# is one of those languages, probably the most important one. So, we cannot run the C# program without the .NET platform. On top of .NET, you need C# itself, or to be more precise, the tools like the compiler and the linker, those tools that take C# source code and convert it to compiled code that can run on .NET. And to write and compile that C# code and also to run it easily, we need some kind of development environment. In this book, we are going to use the default choice, that is Microsoft Visual Studio. So, we have three layers of technology, the .NET platform, C#, and Visual Studio. The good news is we can install the three of them in one shot. If we download and install Visual Studio, it also installs compatible versions of C# and .NET, so the entire installation becomes uneventful as I like it. Maybe you already have Visual Studio, C#, and .NET on your computer, but if you don't, let's install them together. I'm running Windows here, but the procedure is pretty much the same if you're using a Mac. You go to the Visual Studio website, that's visualstudio.microsoft.com, click on Download Visual Studio Community edition. That's the free version. The other versions are commercial, and if you are an individual developer you probably don't need them. One potential source of confusion. There is another free product on this page called Visual Studio Code, and that's a different product that happens to have a similar name. You see,
Visual Studio is an IDE, an integrated development environment. The keyword is integrated here. It wraps together almost all the tools that you need to develop in C#, a very sophisticate editor, the compiler, the debugger, and much more. You could build a complex C# application and never leave Visual Studio potentially. By contrast, Visual Studio Code is more lightweight. Most people would call it a text editor, but don't think of a basic text editor. This isn't Notepad, it's a powerful environment in its own right. And in fact, there is a lot of overlap between Visual Studio Code and Visual Studio, but Visual Studio Code is more lightweight, flexible, and configurable with plugins while Visual Studio is heavier and more integrated and complete right out of the box. And for most of my programming, I use Visual Studio Code, the lighter weight tool. However, some programming languages really benefit from a IDE that is tightly integrated, and C#, I believe, is one of those languages. We'll see a few examples in this book of the way that Visual Studio really helps you when you're coding with a language like C#. Bottom line, Visual Studio Code, the text editor, is fantastic, but when I write in C#, I personally prefer a complete IDE like Visual Studio or other C# IDEs that are available on the market. So, let's install Visual Studio. I'm launching the Visual Studio installer we downloaded, and I gave it permission to put stuff on this computer, and now it's installing.
Soon enough, we can pick which flavor of Visual Studio to install. Visual Studio is not just for C#, it can be used to code with other languages and other target environments.
We want to write code for a .NET desktop environment using C#. We can customize this installation, but by default we already got the C# option. And let's go. It's going to take a while to download and install a few gigs of stuff.
We have a few more steps to go through to complete the installation. Visual Studio is asking me if I want to connect to Microsoft services. Then it asks me to pick a theme. And there we are, installation done.
Now we have Visual Studio and the rest of our development stack, C# and the .NET platform. To get to code as quickly as possible, we can create a new project. And make it a Console Application, a program that you launch from the command line.
And check that it's a C# console application. You should have this tag here. Now we have to give a name to this project, and here is one detail that might look confusing. Like a good IDE, Visual Studio has the concept of a project, but it also has this other concept of a solution. And the first time I saw this, I wondered what is a solution and how is it different from a project. As it happens, the definition of project and solution are a bit fuzzy, and there are a few special cases, but to simplify things a bit, a project is something that produces a single thing, a single assembly. An assembly is an executable file, or a DLL file. And a solution includes multiple projects, each producing its own assembly. So, you can have a solution that includes a web application project, for example, and another project that is a DLL library used by the web application, and a third project that is a bunch of tests for the web application, and each of these projects results in an assembly and projects can depend on each other. So the solution contains projects and also tracks the dependencies between projects. In our case, however, the distinction between project and solution is overkill. We are creating a solution to contain only one project because we need only one executable program. Visual Studio makes it easy to deal with this simple case. When we give a name to the project, let's call it MyFirstProgram, the solution gets the same name by default, and we can place solution and project together in the same directory. I'll pick the default location, but you can change it if you wish. So in this case, the distinction between solution and project isn't very relevant. Our solution includes only one project, but we could always decide to create another project in the same solution later. Next step, we can decide which version of .NET to target. By default, it's the latest support version installed, but let's be cutting edge and select this more recent version. We won't be using many visuals in this training that need this version, but we will mention one at least.
And then I confirm and Visual Studio generates a project that contains a placeholder for a console application. And if you look at it, this placeholder is actually the C# version of Hello World. One thing, though.
Remember what I said in the beginning that I'm using Visual Studio 2019 here, and if you are using a later version of Visual Studio, then you'll get a different version of Hello World, a shorter version that
uses modern features of C#. So, maybe you won't see this first line, or you might even get a super short version of Hello World that has only one line and no class and no main method, and we don't want this. I mean, it's nice to get shorter code, but this shorter version of Hello World hides a lot of boilerplate code from you.
That's good in general, but in this training we want to see the boilerplate code because that code has a lot to teach us about the basic features of C#. Now we have a basic C# program. Now, see this little green arrow here? When I click it, Visual Studio compiles the program and runs it on .NET. It also opens the console to show the result. Here it is, the string Hello World! So, see, Visual Studio took care of everything for us. It generated this little program, compiled it, ran it, and showed us the output. We could do away with Visual Studio and go through this process on the command line, write our code in a text editor, pass the code through the C# compiler, and then generate an executable and run it, but it's just so practical to use Visual Studio to do everything.
We don't have to meddle with the environment, we just write the code and run it. And if we want to tune this process, we can go here in the Project menu, open the project's properties, and, for example, change the version of .NET that we are targeting. Everything convenient and integrated.
So, we are already starting to see the advantages of using an IDE like Visual Studio. So, we set up our system and generated Hello
World. Next step in the plan, let's see what all this code means.
Chapter 2 Exploring C#'s Building Blocks
This is where we make sense of a minimal C# program. We'll use Hello World as an example, but this isn't going to be about just Hello World. Instead, we're going to take a short hike, so C#'s basic syntax. Hello World will be our base camp, so to say, the place where we start from and the place where we eventually return to at the end. During this hike, we'll learn a few things. And when we come back to the base camp, we'll have learned enough to make sense of everything in Hello World, except for a couple of details that we'll leave for the next chapter. And we'll start our hike from this line here.
Whatever language you are coming from, you probably recognize this thing as some kind of function call that takes this string and prints it on the screen. To be accurate, it's not really a function. C# doesn't have functions, but it does have something similar. Let's forget about C# for a moment and recap what a function looks like in general in most programming languages.
It usually has a name and a bunch of arguments, the inputs to the function. When you call the function, you give values to the
arguments.
And then, there is the body of the function. Different languages use different ways to delimit the body. I use the curly braces here. And the body contains code that does something with the arguments and sometimes calculates a result and returns that result to the caller. Not all functions return a value.
Some perform some operation and return nothing. But in the general case, a function also returns a value. So this is what we expect a function to look like in a generic, unspecified programming language. Let's write a function still using this generic programming language, a function that converts the temperature from Fahrenheit to Celsius. Let's give it a descriptive name, fahrenheit_to_celsius.
This function would have one argument. That's the temperature in Fahrenheit. And the body of the function would calculate the temperature in Celsius with this formula. Note that we need these parentheses in general because, otherwise, in most programming languages, the division would take precedence over the subtraction. So we need to specify that the subtraction comes first in this formula. And like that, we have the temperature in Celsius, and we can return it back to the caller of the function. So this is what the fahrenheit_to_celsius function looks like in a hypothetical programming language. Now, let's see what it would look like in C# more specifically. Now we are going to convert this function from the hypothetical generic programming language that we use so far to actually C# code in baby steps because there is a lot to know, even in a small piece of C# code like this. Let's start from the name of the function.
This is technically a valid name in C#. The compiler would be okay with it, but it's not idiomatic. It's not the name that an experienced C# developer would use. Instead, the convention is to write function names with the words joined together and their first letters in uppercase. This is called PascalCase because it was used in the Pascal programming language or also CamelCase because those uppercase letters are supposedly like the humps of the camel. Using CamelCase is a convention, but conventions are important in C#.
Then, the arguments to the function, in this case one argument. It goes in parentheses, so these parentheses are okay. And there is a convention here as well that argument names are like function names except that the very first letter stays lowercase like this. Sometimes this is called lower camelCase. And the same convention applies to variable names like this one. So let's change it to lower camelCase. The body of the function goes in curly braces in C# so we can keep these braces. And this is the indentation style that's considered nice among C# developers. And we can also keep this return. It's a valid keyword in C#. One thing that's missing, however, is in C# each statement is terminated by a semicolon like this. So, so far, we didn't change much from the original generic function aside from naming conventions and minor things like semicolons. Moving on to more substantial things. I said that we are converting this function to C#, but I also told you that C# doesn't really have functions. What it does have is something similar to functions called are actually different kinds of methods in C#, but static methods are the only ones that we'll see in this training and the ones that are more similar to the traditional idea of a function. So first, we have to tell C# explicitly that this is a static method with the static keyword. And second, like functions, static methods cannot exist on their own. They can only exist inside something called a class, so to turn these into a static method, then we need to wrap it in a class. And let's call this class Temperature because maybe it could contain other methods that deal with temperature. Class names and method names are both CamelCase by the way.
So to recap, this is the definition of the class. This is the body of the class, and notice the curly braces again. And inside the class, this is the definition of a method, that is C#'s on spin on the function. Now this code won't compile yet. It's still not valid C#. It's missing something important that we'll see in a moment. But eventually, once we are done making this valid C#, we'll be able to call this method by writing this, name of the class dot name of the method and then the argument in parentheses like 32. And this call, for example, would result in the temperature of 0 degrees Celsius. I have to give you a little heads up at this point. From this example, you see that we created this class Temperature just to contain the method fahrenheit_to_celsius because the method cannot exist on its own without a class. But just be aware that this isn't the entire story about classes. As it turns out, classes are much more than just bundles of static methods. They are actually one of the central concepts in C#. They are the foundation of programming. But is a topic for another day. We don't want to open that can of worms in this training, so we'll treat classes as just things that contain static methods. Just know that as you learn more C#, you'll also unlock the full power of classes. So coming back to our example, we took steps to convert this function to C#, but I told you that this isn't valid C# syntax yet. Let's try it actually. I have the Hello World
example from before here. Same code as before. I just changed the size of the fonts to make it more readable. And if I copy this method and paste it into Visual Studio right inside the class that we already have here, there. See these red squiggles?
They mean that this code is invalid for the C# compiler, and that's because this code is still missing one thing.
Chapter 3 Juggling Types
This code doesn't work yet because it's missing one crucial piece of information, and that is the types of things. C# really cares about types, so let's fix the types in this code. First, we say that this static method takes an argument called temperatureFarenheit, but we don't specify the type of this argument.
Is it a string, a number? If it's a number, is it integer or a floating point number? We need to decide. In this case, we are talking about temperatures that might have decimal digits, so we probably want a floating point number. C# has two floating point types, float and double. Float is the regular one, so to say, and double takes twice as many bytes and gives you extra precision. Both these types are based on types defined by .NET platform. All the types in C# rely on .NET types. So don't be surprised if you see a similar set of types in other .NET languages, maybe with different names. So, coming back to this code, should this argument be a float or a double? In our case, we probably don't need the extra precision, so a float should be enough. We can declare that this argument is a float, like this. And you see that the red squiggle under the argument went away. Likewise, this variable also has a type. It should also be a float, so
let's specify that. One less error. And also, this entire method has a type.
You see, if I hover with the cursor over the name of the method, Visual Studio tells me method must have a return type and currently doesn't. The type of a method is the same as the type of the value returned from the method. So in this case, it's float. We must declare it in front of the method name like this. And this fixes a few more errors. We need to declare the type of all methods in C# with the exclusion of methods that don't return anything. And in that case, we use the keyword void like this to mean this method doesn't have a type because it doesn't return a value. But in general, like in this case, we do have a type. There is one last error in this code, and, once again, it's a problem with types. This one is pretty subtle. Let's look at the types involved in this calculation. This value is a float, remember, and we subtract 32 from it. If we just write 32 like this, this number has no decimal part, so it's an integer value. In C#, the type for integers is called int. So we are subtracting an int from a float. And that's okay because C# just takes the int and converts it to a float, so it becomes 32.0. C# can do that automatically because we are not going to lose any information by upgrading an int to a float. It's a safe conversion.
So we are subtracting a float from a float, and the result is another float. But now, we divide this float by this other number. And the problem we are having is that this number written like this is not a float. It's a double, which you'll remember is more precise than a float and contains more information. A float would be more than precise enough to represent this number, but that doesn't matter. C#'s syntax rules say that when I write a decimal number like here, then it's a double by default. This 1.8 is a literal for a double as it's called. So now we have a float divided by a double that results in a double, and we try to assign that double to a float. And that's when the error happens. We cannot assign a double to a float because we potentially lose information by doing that, so this conversion cannot happen automatically. Note that I said potentially. It doesn't matter which exact numbers are involved and whether or not we actually lose precision. C# won't to let us shoehorn a more precise type into a less precise type period. So how do we fix this? There are a few ways. One is to upgrade this variable to a double like this. But if we do this, we just shift the problem to another place because now we cannot return this variable from this method because we said that the method is a float. So now we have to upgrade the entire method to a double, and that fixes the problem for good.
Another way to fix this problem, let me roll the method and variable back to float. Another way would be to force this conversion and say okay, C#, I know what I'm doing. Do as I say. Just force the conversion of this expression from double to float. That's called a cast.
In fact, if I hover over this error and ask C# to show me a list of fixes for the error, that's what it will propose. Add an explicit cast. And you learn how it cast works in the C# type system training. But for now, we won't do this. We want to use a cast because we have yet another way to fix this code that is quicker and easier than either adding a cast or upgrading the method to return a double. And that is let's go to the root of the issue. That is the fact that this number is interpreted as a double. And let's say look, C#, this number is not a double. It's a float. And we can do it by simply adding this f after the number. Now this is the literal value for the float 1.8, not double 1.8. So now we divide float by float. That gives us a float. And we can assign it to a float variable. And finally, this code is 100% valid C#. There are no remaining errors.
That was a lot of work we did to make the C# compiler happy. First, we had to wrap this static method in a class because methods cannot stand alone without a class, and we had to go through all these hoops to declare the types in the method and fix the types when they didn't quite come together. I promise that this attention to types becomes second nature as you work with C#. At some point, you sort out all the types without even thinking about it. But going through these hoops for the first time taught us a general point about C# and types.
Chapter 4 Static Typing & Built-in Types
We just learned that C# is fussy about types. It wants to know the type of everything upfront, variables, the arguments of methods, the return values. Not all languages work like that. For example, if you take this method and convert it to JavaScript, it's quite similar.
It's almost spot differences. But if you do mark the differences, you see that they are mostly about types. Languages like JavaScript where you don't declare types are called dynamically typed. And the languages like C# where you do have to declare types are called statically typed. You can also just call them static and dynamic for short. Which one is better, static typing or dynamic typing? That's a debate in programming, and I'll carefully avoid that debate here because I think it depends on what you want to do. Dynamic static languages are both useful in their own ways. However, we've already
seen one negative and one positive side of statistically typed languages like C#. The negative side is that the language is more verbose when you declare types. Those comments we made in the beginning about how much code you need in C# to do even simple things, a lot of that is down to this, that you have to declare all those types. On the positive side, static typing is safer. It makes it harder to make mistakes. A lot of bugs in programming happen when you assign the wrong type to something. We've seen it with this line of code. We assigned the double to a float, and that could result in a loss of precision in some situations. Not in this case maybe because of the specific numbers involved, but C# doesn't take any chances. It steps in and prevents us from making this kind of mistake. So static typing helps us catch some errors before we even run the program, and that's pretty cool. So a static language like C# tends to be more verbose, but also safer. Actually, I've got to add something here. This one is the most visible of static typing and one most people would mention, but I don't think it's the most important one. In particular, when it comes to the benefits of static typing, yes, static typing does help you catch some errors. But there is another benefit that I think is more compelling than that, but we'll talk about that in the last module of this training as we wrap it all up. I promised that I would not go into the debate between static and dynamic typing, and now I'm doing it. This is the takeaway from this discussion. C# is a static language. A lot of what C# is comes from this fundamental idea that you are supposed to explicitly declare the types of things. So let's talk a bit more about the types that are available in C# and then we'll head back to the base camp.
We've mentioned a few of the basic types in C#, but there are more. Let's have a super quick look through them so that you know what to expect when you start coding. First, types that represent integer
numbers. We've mentioned the regular ints already, but C# also has all kinds of riffs on the same concept.
A long is like an int with a bigger range, and a short has a smaller range, but it takes less memory. And finally, an sbyte, that stands for signed byte, is the smallest, just 8 bits long. And then there are the unsigned versions of all these types that can only contain positive numbers: byte, ushort for unsigned short, uint, and ulong.
And for good measure, there are also two native integer types that are called nint for native int and nuint for native unsigned int, and have different sizes depending on the platform you are running on. They are only useful for very special cases like programming.
So many options that you might feel overwhelmed, but here's the thing, don't worry much about having to pick the right type. Usually when you pick the type of a variable, memory isn't a big concern, so in most cases you just use an int if the number is not too big, and if you have a reason to think that the number might ever be larger than 2 billion or so, then you pick a long. And you leave the other types for special cases, like you use a byte when you have to represent something that is by its own nature an thing. Then there are decimal types, and we have fewer choices there. There are the two floating point types that we already mentioned, float and double, and there is decimal that is a fixed point type, which means essentially that it's the best choice for numbers that you don't want to approximate and should be exact numbers. The classic example is money. If you do a money calculation and you end up with weird approximations where the result is 99.99999, and so on, cents, that's probably because you should be using decimals for money instead of floats or doubles.
And finally, there are the other basic types: strings, that are sequences of characters; chars that are individual Unicode characters;
and bools, that is Booleans, values that can only be true or false. And with this, we've paid lip service to all the basic types in C#.
There are no more. However, there are more types in C#, they're just not basic types. In fact, there is an infinite number of types in C#, because you can create your own types, and that's what programming is all about. But I promised that we are not going to talk much about programming in this book, so just know that this side of the world of when it comes to the types of the language, you've just seen the entire list.
Chapter 5 Type Inference & Arrays
While I'm talking about having to declare types, there is a small wrinkle about this. I told you that C# wants you to declare types for everything. Like this variable, we have to specify it's a float. However, you might argue that in this case, for example, this declaration is redundant because the compiler already knows this is a float just because this expression here results in a float, and we are assigning it to this variable.
So it's pretty obvious from the context that this variable is meant to be a float. So you might say hey, C#, why do I have to say the same thing twice essentially? Cannot you just look at the type of this expression and understand that this variable is meant to be a float? And in fact, C# does have a shortcut for these situations. Instead of specifying the type, in this case, I can use the var keyword. That means I'm not going to tell you the type of this variable, but you can infer the type from the context from the type of this expression. This is called type inference. Type inference is optional. You only use it if you want to. But if you do use it, it doesn't make your code any less This code, for example is still because now that the compiler knows that this variable is a float, if I try to use it in a
context where I cannot use a float, for example I assign it to another variable that is, say, a string, I get an error as I should.
And I cannot use type inference in places where the compiler has no way to actually infer a type. So, for example, I cannot use var here because there is nothing that gets assigned to this argument yet. So the compiler cannot know what it's supposed to be.
So, type inference cannot work in this case. It can only work in places where the type can be inferred by the context. So, type inference is still safe. It's still static typing, just slightly less redundant static typing.
One last piece of information before we head back to the base camp. Let's talk about arrays, variables with multiple values. Let's say that we want a variable that contains strings; not one string, but many strings, like names of animals.
So, this variable is an array of strings. This is the syntax in C# to declare an array of strings, similar to arrays in many other languages. And you can have arrays of any other type as well. Let's assign a value to this array, like this. The curly braces mean array, in this case, and the values in it must be strings and separated by commas.
And let's not forget the semicolon at the end. Later on, we can reference the elements in this array, the individual strings, with this syntax, square braces, and the index, the number of the element. In keeping with programming tradition, the index of an array starts from 0, so this is the first element, this is the second, and this is the third.
And that's all you need to know about arrays. We can finally return to the base camp, to "Hello World!", and see how much of it we already understand.
Chapter 6 Back to the Base Camp
Finally, we're back to where we started from, the basic Hello World program. So here we are again. I removed the other method that we copied in here and now it's just the basic Hello World again.
Let's see how much of this program we understand now. This is not a test of your knowledge. There are things we didn't quite explain yet so you might have to take a few guesses or rely on your knowledge from other programming languages a bit, but still, it would be an interesting exercise if you want to do it. What is Console.WriteLine. WriteLine is a static method. A static method to define on this class called Console. Actually, if we hover with the mouse pointer over our WriteLine, Visual Studio shows us information about this method, even the documentation that
explains what it does, it brings out its argument and it's a void static method because it doesn't return the value, it just prints the argument.
So this is the important line in this program, but we cannot just type this line and execute it. C# wants each program to have an entry point, the main method. What happens is when we run this program .NET looks around for a method called the Main and automatically calls that method. So the main method is the place where our program begins, and in our case, the entire program is just this line. Main is a static method, that's the only kind of methods we know so far and it's usually a void method like, in this case, because it doesn't return anything. There is no return keyword in here. Do you remember if a method then returns something and then we have to specify the type of the returned value, and if it doesn't return anything, then we declare it void like in this case. Like all methods, Main cannot just stand there on its own, it needs a class to contain it, that's what this class is for. Also the main method has an argument called args and you might recognize the type of args as an array of strings. What is this? The answer is that this array contains the arguments of the program. If we start the program on the command line, for example, we can pass arguments to it, and when you run the C#
program, .NET packs those arguments into an array of strings and it passes that array to the main method and the main method that can decide to do something with these strings. In our case, the program ignores these arguments and we are not passing any arguments to this program anyway, so this array would have 0 elements, but it's here in case we need it. So to recap, now we understand most of Hello World, these lines, and it might not look like much until you consider how much C# we learned to get to this point. We learned about static methods, how they are contained in classes, we talked a lot about types, the whole idea of C# being a type safe language, how to fix us some type of related errors, we browsed through the types in C#, we mentioned type inference as a bonus, we touched on arrays, and finally, we mentioned the main method and how it works. We didn't go in depth in any of these topics, but we had to at least get a broad idea of them and go so the longest model in this book just to understand how it worked and not even all of it yet.
We didn't talk about these few lines here because these lines bring us to a bit of a different world, the world of real life programs that are more complex than this simple thing here, and they are usually made of more than one class with one method. And that's the topic of the next chapter.
Chapter 7 Assembling a C# Program
So far, we've seen just a few snippets of C#, a few lines of code in a file. More interesting programs are usually made up of many files, so in this chapter let's up our game and focus on building a program from multiple files. And also, if you remember, there are a couple of lines in "Hello, World!" that we still didn't explain, because those lines are easier to understand once you know how to write programs with more than one file. In this chapter we'll cover these lines as well, so we're going to give one last look at "Hello, World!". The file that we just added to the project contains a class with the same name as the file. That's a common practice to have one class per file, and the same name for the file and the class.
It's certainly not mandatory, but it's a convenient default way to organize classes in files. And this class contains a few static methods, one, two, four methods. It's like a tiny library to do weather calculations.
These methods build upon our earlier example. So we have the conversion from Fahrenheit to Celsius, so you probably remember this one, and also the other way around, from Celsius to Fahrenheit. I also added this method that calculates something called the ComfortIndex. It takes a temperature and humidity in percent, so a number from 0 to 100. And it returns an arbitrary index that essentially tells you how pleasant it is to do physical exercise at this temperature and humidity. This idea of comfort is a vague concept. There are many possible ways to calculate comfort or perceived temperature and similar indexes. This formula that I found on the
internet is just temperature plus humidity divided by 4, and the lower the index, the more comfortable it is to do physical exercise. It's certainly not a very reliable index, but it's simple and it kind of works for average temperatures and humidity, so it will do for now. And finally, we have this Report() method. it takes a string that's supposed to be the name of a place and its temperature and humidity and prints a short report on the screen that shows the comfort index for this location. The + operator applied to strings means concatenate these strings. So this is the string Comfort Index for, followed by the name of the location, then a colon, then the ComfortIndex calculated by this other method. Note that the ComfortIndex is not a string; it's a float. But when we concatenate it to a string, C# converts it automatically from float to string. We know that C# is fussy about types, so it won't let us convert types implicitly, unless it's a safe conversion we're talking, about and this one is a safe conversion. You can always convert a floating point number to a string without getting an error, so it's okay to have a float concatenated to a string, like here. So, this is the class I came up with. I called it a library class. But speaking of that, we need to take digression about this concept of a library. I told you that I wrote this class that contains utility methods, and it's like a little library. And I this class in the project to use it from our program. Just to be clear, I don't want to give you the wrong message and encourage you to code like that. In most cases, when you want to share functionality like a library, you import the compiled library, not the source code of the library. If you're used to compiled languages, you're thinking, of course. But if you're more used to interpreted languages, then it's worth spending a moment on this distinction. You remember that I told you earlier on that a C# project results in an assembly, a file or sometimes a small bunch of files? So, let's say that we have a project that delivers a Windows executable, and this
project depends on a library, a DLL. In general, you don't need the source code of the library; you just take the compiled library, a DLL file, put it into the project, and tell Visual Studio that the project depends on the DLL. Then when you compile the project, Visual Studio will put both the executable and the DLL in the same directory so that the executable can see the DLL and everything works at runtime. And there are other ways to make a DLL visible to a project in Visual Studio. You can put the DLL in a global area on your machine that's visible to any project or install the library from the internet using the NuGet package manager, all stuff that's outside the scope of this book. But one thing is common to all these cases: you don't the source code. You generally share compiled code, not source code. The reason why I source code in our case is that we want to modify this code, and that's what we're going to do next. There are a few things that we can improve in this class before we go on and use it in our program, small things, but they kind of add up. First, we could add comments. I did try to make this code to use explicit names for variables like here and so on, but sometimes you just need a comment. For example, we said that this formula that calculates the comfort index is probably not the most accurate way to do it, so let's write a comment about that. If you type a double slash, everything that follows is a comment, up to the end of the line. To be honest, this is the kind of comment that I end up regretting because then I come back to my code after a while and I should have fixed this code instead of commenting that it's not so great. But in this case I plan to stop updating this code as soon as I finish this writing book, so maybe this time I'll get away with it. Another thing worth commenting is, when we call ComfortIndex(), we need to remember that higher indexes mean less comfort. That's not very intuitive. It's easy to get wrong. So let's write a comment about
that. We've seen comments with a double slash, but there are other flavors of comments in C# as well.
You can write a comment that spans multiple lines, delimited by a slash star sequence in the beginning and a star slash sequence in the end. And many people also had a few more asterisks to make it look nicer. And you can also write comments with advanced formatting, used to generate documentation for classes and methods and so on. But double slashes are probably the most common way to comment code. Besides comments, another small improvement we can make to this class is, this method defines a variable and then returns it, but we don't need this intermediate variable. We can do the calculation and return the result straightaway. It's a little refactoring, as they call it, an internal change that doesn't change the behavior of the code, because this method returns the same result as before. But most programmers would find it a bit easier to read. These two methods also create temporary variables that we can remove, but this time, instead of doing it by hand, let's ask Visual Studio to do it for us.
You can on a variable and pick Quick Actions and Refactorings, and then Inline temporary variable. That means what we just did, remove the variable and replace it with its value. See, Visual Studio shows us what it's going to do. This is the code we have now, and this is what we'll have after the refactoring.
I click, and it's done. The variable has been inlined. And we can do the same thing here. Automated refactorings are super powerful.
You have different refactorings available in different contexts, and some go way beyond inlining a variable. You can ask Visual Studio to bend and twist your code in so many ways. For example, this string concatenation. It's a bit of an eyesore with all these snippets of string joined together.
C# has a more elegant way to combine strings called string interpolation. And if you open the refactoring menu, Visual Studio offers to convert this concatenation to an interpolated string, so let's do it.
This $ prefix means that this is an interpolated string, which means it contains snippets of code in curly braces. So C# evaluates these snippets, it evaluates this variable, and calls this method and embeds the results in the string. Nicer code, more readable, and we did not have to write it by hand. Visual Studio did it for us. We improved this class. It's certainly not perfect yet. For example, one confusing details is that takes the temperature in degrees Fahrenheit, while a Report() takes it in Celsius and then converts it to Fahrenheit before calling ComfortIndex(). So, this code is more convoluted than it could be. Now we have two files in our project. One contains the WeatherUtilities class, and the other contains the "Hello, World!"
program from the previous module. Let's keep both files visible on the screen. I can drag this tab and drop it, and now I can see both files at the same time.
Let's write a program that uses the Report() method to compare the comfort indexes for different cities. First, I'll go to our Main() method and delete the line that prints on the screen. And instead, let's call WeatherUtilities.Report. The first argument to this method is supposed to be the name of a place.
And then we need the temperature and humidity for the report, so I looked up the temperature and humidity for the month of May, as an example. The average temperature is nice, about 23 °C. That's 73 or 74 °F. And it's a bit humid, about 65% humidity. Let me put a comment here with equivalent temperature in Fahrenheit, about 73, in case you're not used to Celsius. So we wrote the code that calls this method, but now Visual Studio is complaining about something. We have a red squiggle under the name of this class here. I can hover the cursor over the error, and it says that this name isn't visible here.
And the reason why the name of the class isn't visible is that when I wrote this class, I wrapped it in something called a namespace. Namespaces exist for a pragmatic reason, to avoid name clashes. Most projects are made up of many files and classes, and most also use libraries that add even more classes. So in a large project, you can end up with thousands of classes, and it becomes quite likely that you'll have two classes with the same name. For example, if you're writing an application to manage an archive of documents, you might have a class named Document in your application, but maybe you also use the library to parse XML that also contains a class named Document, meaning an XML document in this case, so you have a clash. When you say Document, C# doesn't know which of the two classes you're talking about. That's what namespaces are for. If two classes belong to different namespaces, then they can have the same name because you can reference them by their qualified names, as they're called, that is the namespace, dot, the name of the class, and then you don't get a name clash. You could still have a clash if you have two namespaces with the same name that contain classes with the same name, but that's unlikely to happen in practice because developers tend to use the name of the project or the library as a namespace, like MyCoolArchive, in this case, or sometimes the name of the company they work for, and those names tend to be unique. And also you can have nested namespaces, namespaces within namespaces, like google.XML, for example, and those nested namespaces make a name clash even less likely. So that's the reason for namespaces. And when I wrote this class, I wrapped it in a namespace. And when Visual Studio generated the main program, it also put that program in its own namespace with the name of the project.
You might wonder why Visual Studio created a namespace here that looks unnecessary, because if you think about it, this class exists just to contain the Main() method, and the Main() method is going to be called by the .NET platform, not by us. We're never going to write MyFirstProgram.Program.Main(), for example. So why bother with having a namespace here if we're not even using the name of this class? Isn't that overkill? Thing is, first, it's considered bad form to have a class without a namespace. Second, if you have a class without a namespace, C# still assigns the class to a default anonymous namespace. So in the end, you still have a namespace, so why not make it explicit at this point? By the way, while we're here, this class, let's rename it to something more appropriate, like CheckComfort.
Now that we know about namespaces, let's fix this error.
Chapter 8 Importing Namespaces
This class belongs to this namespace, and this class belongs to this other namespace. If they were in the same namespace they would see each other, but they're not, so we cannot just use the short name, WeatherUtilities here. We have to use its fully qualified name, like this. And now the error went away.
We have another error now, but the previous one about not being able to see the class, that one's fixed. On the other hand, in C# you use class names all the time, and you probably don't want to use qualified class names all the time. That would make the code really verbose. Instead, you can write something like this at the top of your file. This using directive means, whenever I use this name WeatherUtilities in this file, what I actually mean is this qualified name.
Essentially, this is an alias for this, and now I can use the alias instead of the fully qualified name. And we can make it even shorter.
Instead of doing this for each class that we want to use, we can just say import all the classes from this namespace, like this, and now we can use the classes in MyUtilities by their short name. In this case, there is only one class in that namespace, and we can just use its name.
So, that's how you deal with namespaces in most cases. If you need even one class from a namespace, you just import the entire namespace, like this. Sometimes you might get a clash. Maybe you're using two classes with the same name from different namespaces in the same file. In that case, you get an error, and you can fix the issue by using the fully qualified names of the classes maybe, or sometimes importing the specific classes you need instead of the entire namespaces like we're doing here. But in most cases, you just import the entire namespace. One last thing about namespaces. What's this using System line doing here? What's this System namespace? The answer to this question is also a chance to check off an item that has been on our list for a while, completing our knowledge of the "Hello, World!" program. Previously, we checked "Hello, World!" line by line, except for a few lines. And now that you know about namespaces, we can go back to those few lines and finish the job. The class in "Hello, World!", like any class, is wrapped in a namespace.
When Visual Studio generated this code, it also gave a name to this namespace, the same name as the project. And then there is a using directive here. It's importing a namespace called System. System is the most useful namespace in all of .NET. .NET comes with many core libraries, and one of them is System, that contains the most important classes, in a way, the ones that you want to have handy in most programs. And one of those classes is Console, that gives you methods to input and output data from a program. So if we want to call Console.WriteLine and print a string on the screen, we need to import the System namespace. That's where the Console class lives. So, in the beginning I told you that our first goal would be to understand "Hello, World!", all of it, and we finally do. It took a while. We could have explained it in two minutes, really, but only if we had skipped all the details. And now that we did take the time to explain them, I hope you agree that those details are important. In most languages you see the code for "Hello, World!" and just move on. But in C#, going through "Hello, World!"
like we did teaches you a lot, not just to print a line on the screen. That was the easy part. Yes, it's just "Hello, World!", but now that you know how it works, you also know a lot of the building bricks of C#. So, we're finally going to say goodbye to "Hello, World!", but I'll use this code to tell you one last thing.
When we looked at this code in the beginning of this book, I said look how much code we need to print something on the screen; however, you could write this program with less code as long as you're running at least C# 9 or later. C# 9 introduced a feature called statements. That means that you can remove all the boilerplate from this program and just write it like this, without wrapping it in the main method and the class and the namespace.
You import the System namespace to get the Console class, you call Console.WriteLine, and that's it. And by the way, C# 10 introduced yet another feature called the global using directives that allows you to skip this first line and make this code even shorter, a Now you say, are you kidding me? We spent like two modules talking about how much code we need to write Hello World, and now it turns out that in modern C# all that code is unnecessary. And, well, the thing is, yes, C# these days has features that allow you to write less code, but those features are syntactic sugar, as they say. They're just a nicer syntax for code, but they don't change how C# works under the hood. Under the hood, C# still wraps the call to Console.WriteLine in the Main method and the method in a class and the class in a namespace and it imports the System namespace, it's just that it generates all that stuff automatically for you. Features like statements are nice, they make a short program look better, but they don't change the essence of C# code. As soon as you start looking beyond a short program, you still need to know about classes and namespaces. So when you're learning, it still makes sense to start from the more verbose version of Hello World, and that's why we kind of ignore the features like statements so far. And with that, no more Hello World, ever again. Back to fixing compilation errors. Earlier on, we had an error here because we couldn't see this class. We fixed that error by importing this namespace, but now we have another error in this method. Why does it happen?
I'll explain it with another example. Imagine a class that translates text from English to Japanese using an online translation service, maybe. So it has two methods called JapaneseToEnglish and
EnglishToJapanese that translate in the two directions. And maybe these methods have a lot of work to do before and after calling the online service. Maybe they convert symbols from a Japanese phonetic system to writing letters, or the other way around. And then let's say that at some point, they both call another method named CallOnlineService that does the work of pushing the data to the online translation service and getting a response. So three methods in this class, but only two of them are meant to be called from outside the class. They are public, part of the interface of the class. The third method is only called from other methods inside the same class. It's private to the class, an implementation detail, and you want to hide it from the outside world. Coming back to our WeatherUtilities class, there are four methods here, and most of them should be public. They exist to be called from outside the class. In C#, there are two keywords, public and private, to decide what kind of accessibility a method should have. So you can say that the method is public like this. Actually we have to do this, because the default is that if we don't specify the accessibility of the method, then it's going to be private. So, we should make these methods explicitly public. We can make an exception for the comfort index method. We don't need to call it from outside the class, only from inside the class, for now at least. So, for the time being, we can keep it private, or even make it explicitly private for clarity, like this. If we ever need to call it from the outside, we can always change it to public later.
And now we have four methods, but only three of them are part of the interface of this class. C# also has other access modifiers that
are more specialized, for example, internal means that a method is visible to any class inside the same assembly, but not from other assemblies, and there are more, but you learn them when you need them. Public and private are the most common, by far. And by the way, from the moment that we made the report method public, the error in our main program disappeared. We have no errors here anymore. We can finally run this program. Click on the Run button, and there we have it, the comfort index. We took it in small steps, but now we have a working program made two separate classes. Let's flesh it out a little bit more. This call reports the comfort index in May. Now, let's say that together with a bunch of friends, we are looking for places to visit next May. Let me print a little title here, Where should we go next May? And we'd like to compare the comfort index for a few different cities, this city and others. Here in the Quick Actions menu, Visual Studio is giving us a bunch of suggestions to fix this problem. Maybe you're talking about a new thing that is called Consolle with two l's?
Should I generate it for you? Or maybe you mean the Console class with one l, and should I fix the spelling to reference that class? This last suggestion is what we want, so let's select it, and Visual Studio the spelling of this class name for us. We'd like to compare the weather in this city and other cities. So I'll add a second call to the report method here. Note that we don't have to type the name of the class and the method letter by letter here. We can use in Visual
Studio like in any good modern development environment. So I just type the W here, select WeatherUtilities from this list, type a dot, select the Report method, and when I type the opening parentheses, Visual Studio types the closing parentheses automatically, and shows me the definition of the Report() method so I can check the list of arguments.
First, the location, let's say San Francisco. Then the temperature and humidity. I Googled for it. In May, San Francisco has a temperature of about 65 Fahrenheit on average and average humidity of 73. However, let's not get confused.
This documentation here reminds us that this method wants the temperature in Celsius, not Fahrenheit. That convention worked well for my own city because I found this average temperature on a European site, but for San Francisco I found the temperature in Fahrenheit when I Googled, so let's convert it to Celsius because we have conversion methods as well.
And finally, another city we'd like to visit, let's say Denver, another U.S. city. So I found the temperature in Fahrenheit is 77, and this code converts it to Celsius. And the average humidity of Denver in May, according to my note is 55%.
So, let's make this comparison and finally run our program! The comfort index is better, lower than either SF or Denver.
Chapter 9 Hunting for Bugs with Debugger
The previous module closed on a cliffhanger. We found a bug in our program. We printed the comfort index for these three cities, and the result looks wrong somehow. There must be a mistake somewhere in our code. So let's debug and find it. The process of debugging tends to be different in different languages. In some languages, you typically debug by typing commands in an interactive interpreter. In other languages like C#, people tend to use the debugger of the IDE, because IDEs like Visual Studio come with their own powerful debuggers. This chapter is all about that, using the debugger in Visual Studio. And the first thing you should do to use the debugger, we should first check that we're using the debug configuration to compile this solution, not the release configuration, because the release optimizes the code, makes it more efficient, but also removes the information that's essential for debugging, so you cannot use the full power of the debugger in the release configuration. Most programmers write their code in the debug configuration, and switch to release right before delivering the final assembly. We're in debug mode already. Before we start debugging, let's set a breakpoint. In case you don't know, a breakpoint is like a stop sign for our code. If I click here on the left edge of this line, for example, I set a breakpoint, and when we start the program in the debug configuration, the program runs until it hits this line with the breakpoint, and then stops.
Note that we cannot set a breakpoint anywhere. I could not set a breakpoint on a class declaration. We need the line of executable code, a statement that does something so that the program can stop there. So we have a breakpoint on this first statement. If we start the program in the usual way, it starts and pauses right before it executes that statement. The Run button became a Continue button, and we got a bunch of new toolbars and windows specialized for debugging. Let's use them. Now we want to step through the code and see what's happening, the values of variables and so on.
To step to the next line, I can use this button like this. This moves to the next line. Actually, I'd step over this line too, and the next, and go to the third call to the report method, because that's the one that looks suspect. Now that we're here, if I step over again, the program will complete and exit. Instead, I'd like to step into this method code to see what happens inside the report method. I can do that with the button, and if I click it, here we are inside the report method. Now we can check the values of the variables that are visible from this code. In these windows here, there is a local window that shows all the variables that are visible at this point in the code, but sometimes you might have too many variables here.
Not in this case, but if it happens, there is another window called Autos that only shows variables that Visual Studio decides look more relevant to the current line of code. Usually that means the variables that are used on this line or the lines around it.
In this case, I think that we have all the variables we need here in the Autos window, that is the arguments passed to this method, the current location, temperature in degrees Celsius, and humidity. Let's step through the code once again to calculate the temperature in Fahrenheit, and the result is a bit over 73 Fahrenheit.
That looks reasonable as a conversion of 23 Celsius. So this conversion makes sense so far. I don't see any obvious errors. So maybe there is something wrong with this calculation of the Comfort index. We could execute this line and see what happens, or another way to do it, just to show you, is to switch to the Watch window here, and add a permanent watch for this code, so that it stays here, always updated as we step through the code.
In this specific case, I think a watch is a bit overkill maybe, but if you are debugging a more complex piece of code, this feature can come in handy. S
o calling Comfort Index with these values returns the result of 34.6, that is, the same number we got before on the screen. And if we look at this formula, it seems to make sense, so I don't see any glaring errors here.
Maybe we're looking for the error in the wrong area of the code; that's part and parcel of debugging. Sometimes you just have to interrupt it and restart from scratch. We run the program, and once again it stops on the breakpoint. Earlier on, we looked at the code in the report method, and it looked okay. We didn't see anything in it that was obviously broken.
So maybe it's this method that is the culprit, the FahrenheitToCelsius method. I'll go straight to the definition of this method. This is a useful feature. Go to Definition, and then let's resume the execution, and stop again on this line.
We could set another breakpoint here and press Continue, but there is a quicker way. We can just say Run to Cursor, and the execution continues until it reaches the line where the cursor is. So let's check this line. We got the temperature of 65 Fahrenheit, it's written here in the Autos window, or we can also check the value of this variable by hovering over it, 65.
But what's the value of this entire expression? We can check it in a few different ways. We could add a watch for it like we did before, but there is also another way. I'm doing things in many different ways here to show you the features of the debugger. There is a window half hidden here called the Immediate Window, and this one is kind of like the command line interpreters that it would use to debug in some other languages.
Sometimes a command line is the easiest way to debug in C# as well. We can write the name of a variable, or an expression, a method call, anything, and it gets evaluated. So let's copy this line here and paste it in the Immediate Window, and we get a result of 47+ degrees, and what? This does not add up. Even with my limited
intuition for Fahrenheit, I know that 65 Fahrenheit is a pretty normal temperature, while 47 Celsius is scorching hot, definitely not normal for a U.S. city in May.
There must be something wrong in this line, and by now you might have spotted the mistake. When I wrote this code, I forgot the parentheses here.
So the division is taking precedence over the subtraction, and the result is all wrong. Let's press the arrow up to retrieve the previous line, and see what happens if we add parentheses, and there, now 65 Fahrenheit gives us 18 Celsius, which is a reasonable result.
So that was our bug. Let's stop the execution, add the parentheses, and remove the breakpoint by clicking here again. The Comfort Indexes for these three cities are not that different from each other anymore, but Denver is slightly better, and these results make a lot more sense than Bologna being better, as much as I love that city. Great food, but not the best weather! I wanted to make a point. That is, the debugger in a sophisticated IDE like Visual Studio is
very powerful. We only scratched the surface of how powerful it is. I showed you a few basic features, but the more you dig into it, the more you find advanced features that help you debug complex pieces of code that go way beyond our toy example here. For example, I showed you how to place a breakpoint, but you can also add conditions to a breakpoint, like only stop if a specific variable has this value, or only stop on the third time you hit this line.
And also, I showed you how to read variables on the Autos and Locals windows, but it turns out you can also modify the values in these windows to experiment and see what happens if you have different values in the same code, and many other powerful features. In general, the debugger in a C# IDE is worth mastering. It's tempting when you're debugging to think, oh, this bug must be easy to find. I'll just add a call to Console.WriteLine to print out the values of a couple of variables, and I'll find a bug in a minute. And then you start going down the rabbit hole, and before you know it, your code is full of calls to Console.WriteLine all spread around, and you made no progress in finding the bug. You waste time like that, and they have to start again with a more thoughtful approach using the debugger. So, spend some time to learn the features of the debugger, and as soon as you find a bug, reach for the debugger first. Set your breakpoints, set your watches, use these power tools. And with that, we are approaching the end of this introduction to C# code. There is just one last thing I want to tell you about. We are reaching the end of this training. In the beginning, I showed you this list of goals. I said that we wanted to generate a Hello World
program and understand all of it. We did that. And also we turned that tiny program into something more realistic, a mini application composed of multiple files, and we saw how to use the debugger to find and fix a bug in that application. And we covered quite a few details along the way from C#'s types to methods, classes, namespaces, access modifiers like public and private. Actually, we covered an impressive amount of stuff. However, the last point here is something I really want to tell you about, and there's something about the philosophy of C# and the reason why I personally think it's not just a very popular language, but also a great language to work with. So the next chapter is less about objectively looking at C# features and more of a kind of advocacy.
Chapter 10 C# and Smart IDEs
We started this training on a bit of a negative note. I said, look how much code you have to write in C#, even to do simple things. And we saw a couple of reasons for this verbose code. One is that in C#, you have to comply with quite a few formal rules. Put your code in a method, the method in a class, the class in the namespace, lots of boilerplate. We saw that you can skip some of that boilerplate, but only sometimes. So usually you have a lot of code. And another reason for this over abundance of code is that C# is a static, or statically typed language, in the same family as other static languages, like Java or C, and the static languages require you to declare types, and all those type declarations add up, eventually. So the natural result is that C# source code doesn't look as slim in general as, for example, JavaScript or Python code. And we also said, well, on the other hand, the static languages tend to be safer. Those type declarations help you catch some bugs. Now, that is true up to a point. Type safety is a benefit of static languages, but that there is a more compelling reason than type safety to like C#'s approach to types and to code in general. That's the real flipside, the real benefit of having all that extra code and those type declarations. The most important benefit of all the strict rules and type declarations in C# is that once your code follows those rules, making sense of the code becomes easier, not just for you or other developers, but for your IDE as well, like Visual Studio. All that extra code in C# means that even a piece of software can analyze your program and understand a lot of what's going on, and then give you powerful tools to
manipulate that code, tools that tend to work better in C# than they do in many other languages. And indeed, throughout this book, I casually slipped in little moments when I used those automated tools. For example, I used features like AutoComplete to write code, or AutoCorrect to fix errors in the code, and AutoCorrect is not just about fixing errors. It can also fix the style of your code. For example, I just realized that I declare that this methods says static public, but the officially recommended style for method declarations is that access modifiers like public should come first. It should be public static float, and the same for these other methods, but I don't have to remember these details.
I can ask Visual Studio to reorder the modifiers for me. So it helps me fix errors and also to keep a uniform coding style.
Small things, but especially important on large projects with many developers. And besides AutoComplete and AutoCorrect, Visual Studio has other features that take advantage of the IDE understanding the code, like navigation features to jump from a method to its declaration quickly, and so on. We also saw how smart the debugger is in Visual Studio, and that's also in part because Visual Studio understands your code, so it can do things like only show you the most interesting, most relevant variables at a given time. And also
the refactorings we did. Remember when we asked Visual Studio to remove temporary variables, or replace a string concatenation with the string interpolation, and that was only basic refactorings.
There are plugins for Visual Studio or other C# IDEs that give you amazingly powerful automated refactorings. Sometimes I can go on coding for minutes and I barely ever type code. I just use refactorings to change code like I want it.
And Visual Studio sometimes can even generate snippets of code for you. In the beginning, we generated a Hello World program. That was a terrible example, maybe, but you might remember that at some point, Visual Studio said, what's this console with two l's saying? Maybe you want me to generate it for you? We did not use that feature, but it was there. So yes, you have to write a lot of code in C#, but you often don't type all that code yourself. Your IDE
is always there to help. So all those automated tools add up to a different coding experience than many other languages. Just to be clear, automated tools are not just a C# thing, or even a static language thing. You have these tools in any modern programming language. However, my experience is that most of them tend to work more reliably in C# than in most other languages. If you apply an automated refactoring in JavaScript, you usually need to be a bit more careful to rely more on the tests that you hopefully wrote, because your IDE cannot possibly understand everything that's happening in your code. So the refectoring might break something. You rename a method, you might break the code that calls the method. In C#, it's easier for the IDE to track, for example, which code is calling which method, so it's generally less likely that an automated tool will break your code. I love dynamic languages, but I must confess that many of those automated power tools feel smoother when coding in C# than they do in most other languages. They just feel good. Even large systems sometimes feel smaller and generally pleasant to work with. And that's the point I wanted to make. The flip side of C#, being slightly pedantic about types and the way that your code is organized, is that C# code is easier to understand the process for your IDE, and in turn, that results in great reliable automated tools. As professional programmers, we know that writing code is not just about literally writing code. Most of your time programming is spent reading and changing code, and C# is great at that. Together with Java, that's kind of its twin language, C# is a fantastic language when it comes to automated coding tools. With that, we can close this training. I sincerely hope that you enjoyed this book, and it was helpful. I hope I helped you take the first step into C#. It's a language worth building a career on.
BOOK 3
C#
CODING FUNDAMENTALS
CONTROL FLOW STATEMENTS AND EXPRESSIONS
RICHIE MILLER
Introduction
Program flow is one of my favorite topics in development because in many ways it really describes the heart of programming. You already practice program flow every day, even if you don't realize it yet. We have Bob, and he has a routine, just like all of us. Every morning when he wakes up, his life unfolds differently based on various factors. The tasks he manages that day and the order he completes them in change throughout the week. On Monday, Bob wakes up, goes to work, and then comes home to eat dinner and relax. He repeats this basic routine of activities throughout the week. Of course, there are minor variations in what he eats or what he does at work, but that core loop stays the same. On the weekend, though, Bob's routine is entirely different. Instead of going to work, he goes to the park with his dog and goes out with friends at night. The key concept here is that Bob's activities and the order that he completes them in change based on various factors. Each day is a routine composed of constants and variables. Getting out of bed happens every day of the week. Going to work occurs every weekday. Dinner happens every day of the week, but with different food choices. And some tasks are just completely different every day. Over time, we are programmed to perform these types of routines in order to accomplish larger goals. Well, these same ideas apply to our applications. When many people think of an app, they often imagine many lines of code that simply execute an order and produce some type of
result. In some cases this is true, but just like our own routines, as applications grow in maturity and complexity, things are not always this simple. Most significant apps must execute different pieces of code in different order based on various conditions. Maybe the first few lines execute, but then the next several lines perform a task that only admins should be allowed to do. Instead of running these immediately, the code jumps over to another file that can decide if this user has the right permissions to do that. If they don't, that code section is skipped, and instead different logic runs that tells the user they don't have access. This is a simple example of program flow. Program flow manages the order and conditions under which different parts of your application code are executed. It provides structure around when and how our app takes certain actions or makes decisions, handles repetitive tasks, and so on. If you're reading this book, you probably have some essential C# skills, like knowing how to create and utilize variables. You understand what code files are, and you're probably familiar with the general structure of a C# program. These are critical building blocks to get started, and they are the pieces we'll start to control with program flow. This book will help you leverage that foundation to start building more meaningful and complex apps. We're going to tackle common program flow challenges in C#. We will answer questions like how can my app make decisions based on specific data or how can my program repeat a task many times, or even more generally, what other types of actions can my program take to influence program flow? Well, before we start to answer these questions, let's first set up a simple application we can use to experiment with.
Chapter 1 How to Setup the Sample Project
Let's explore the sample app we'll use throughout the course to learn about program flow with C#. So, let's say a Company is a regional chain of coffeeshops that continues to grow. In order to optimize that growth, they would like to get an idea of how their products and services are resonating with consumers. The marketing team has quickly put together a quarterly customer survey to accomplish this goal. The surveys were sent out to customers, and over time most of the results have finally come back in. Unfortunately, the survey tools are very basic and can only export the results in simplistic data formats, so the marketing team has asked the developers to analyze this data for points of interest and to answer certain questions. And that's where we come in. We will use a simple C# console application and leverage control flow, statements, and C# logic to work with this data. Inside Visual Studio I have our project open, and you should see this solution node. A solution is basically just a container for projects, and we have one project, which is our reporting application. There are currently a few files of interest in this project. The first is our Program.cs, which should hopefully be a bit familiar, so let's open that up quick.
Program.cs is the main entry point into our C# app. When we start our application, the main method here will execute whatever is inside of it. At the moment, we just have some code to display Hello world in the console. In terms of program flow, everything will start here. To make sure the app compiles and runs correctly, let's press the Start button at the top of Visual Studio to launch the app. So right away we should quickly see a Hello world message printed out to the console. This means everything is set up fine, so let's close out of here and return to our project.
Now over in our Solution Explorer, we also have a file called Q1Results. This file contains a class that holds some sample survey results data. In C#, classes represent objects or ideas in our code, and they can hold data for us using properties like our CoffeeScore or FoodScore that we see here.
To keep things simple, I have just hard coded some values in here to represent the aggregated survey data. Now, you don't need to understand classes to work through this book. All you have to understand is that this file contains some basic survey results data in it. In a real app, you would read this type of data in from another source, like a database or a web service or a text file. We are skipping that step so we can instead focus our energy on statements and program flow. The last file in this project is called SurveyExample.html. This file is really just here for illustrative purposes. We will not be working with it hands on at all in this book. So for example, if I were to and say Open Containing Folder, this will open our project on the file system. This displays an example survey with the types of questions that the Company asked their customers.
The answers to those questions are represented in the data we just looked at inside of our Q1Results. In order to start working with our
survey data, we have to start writing statements and introducing program flow. Let's get started with statements next.
Chapter 2 Understanding C# Statements
At the start, we explored the idea of controlling program flow through decisions and actions in our code. Well, C# accomplishes this goal through the use of statements. Statements are simply basic instructions that a C# program executes to take action. They are small, complete tasks that can be assembled into very complex logic. The order in which these statements collectively execute is called program flow. If you've written any C# code at all, you've probably already used statements.
A statement can be as simple as declaring and assigning a value to a variable. Basic math calculations are also done using statements. And if you've ever written a string or value to the console, that's also a statement. We can also execute just about any function or method using statements as well. Simple statements like these must end in a semicolon, much like a period with sentences, which is an easy way to identify them. Because program flow is composed of different types of statements, those statements can also influence the flow as they're processed. Simple statements like the ones we just
looked at are often simply processed in order one line at a time; however, some special types of statements can also create branching paths between other statements or functions in your code. Other types of statements can even cause a block of code to run multiple times in a row. So, for example, earlier we discussed common questions, such as how can I decide which code to run based on certain criteria? Well, in this case, we can use a selection statement, which is commonly known as an if/else statement.
A selection statement lets us define multiple blocks of code using curly brackets and decide which one should execute based on some criteria. So maybe if it's Monday, let's send a more noticeable phone call reminder, but on the other days just send a text message. This is an example of a statement altering program flow since only one block of code or the other will run. We also asked the question, how can I repeat the same task over and over again with minimal work? Well, this is where iteration statements come into place, which are more commonly referred to as loops.
Loops define a block of code that executes repeatedly until some condition is met, such as sending an email to every customer in a mailing list until we run out of customers. The program flow will not continue past this point until the loop is done, which, again, is a way of controlling program flow with statements. Selection and iteration statements are just two types of statements, and we'll explore them in much more depth later, but there are many others as well. We already saw an example of declaration statements, which can be used to create variables. Expression statements handle common tasks like calculating values or executing methods.
Exception handling statements allow us to catch and address issues in our code. Again, statements are complete instructions that actually do things in the C# language, and there are additional options beyond these essential types listed here. You may have noticed already that statements can span one or many lines in your application.
Single line statements are simple. They're just one line of code that executes a task and ends with a semicolon. Each of the lines here is a statement, and together they form the logic of a complete function. Multiline or composite statements are a bit more interesting.
These statements utilize additional embedded statements or blocks of code to execute that are defined with opening and closing curly braces. Curly braces are used throughout C# to mark the beginning and end of scopes or definitions, such as a method or a class. These blocks of code can contain a list of nested statements to perform additional logic. Composite statements usually leverage special C# keywords, which is something we'll get into later. There are many types of statements. For this book, we are interested in the most essential statements that can significantly impact program flow. First, we will focus mostly on simpler types of statements and related expressions. Next, we will explore how those concepts tie into selection statements to impact program flow and then the same for iteration statements later on. Finally, we will bring together many of these concepts to look at additional ways to manage flow. So, let's start by looking a little more closely at some single line, simpler statements.
Chapter 3 How to Write Simple Statements
Let's start to get comfortable writing statements by solving a simple task in our app. So back in Visual Studio, let's revisit our main method here.
Right now, this method just prints out a simple message to the console like we saw. Throughout this book, we'll be working with our survey results data, so let's see how we can start to work with that data using statements. As a simple example, let's say the marketing team would like to know the percentage of people who responded to the survey. They would also like to know that specific number of unanswered surveys so they can start to track target numbers for improvement. We do not have these specific values readily available in our dataset, but we can easily calculate them with a couple of statements. Over in our main method, let's stub out a couple of variables to hold those desired values.
First, let's create a variable with the type of double, and this is an easy option for working with numbers that have to support decimals, and just call that responseRate. Also make sure to end this statement with a semicolon. On the next line, let's create another double called unansweredCount, of course with a semicolon at the end as well. Variables are created to hold data, so let's calculate the values we want to store. On a new line, let's say responseRate =, and remember in C# a single equal sign represents assignment, so we need a value to assign to this variable. To do that, let's access our survey data. So I'll type Q1Results to access that class or dataset, and then we can use the dot syntax to access its properties or the data on that class. Visual Studio will show us all of the available properties we can access, so let's choose the NumberResponded. To get the average, we have to divide this by the number of people surveyed, and, as always, make sure to end the statement with a semicolon. The result of this calculation will be stored in our variable for later use. We want to follow those same steps to find the number of unanswered surveys. Let's take care of that on the next line to reinforce this pattern. So let's type unansweredCount = Q1Results.NumberSurveyed -
Q1Results.NumberResponded, and that should give us our second value. Next, all we have to do is display these values to the user when the app runs. Well, further down we have a third statement that just prints Hello world when the app runs, so let's change that to say something more useful. In C#, we can prefix strings with the dollar sign to make working with variables easier. This special character allows us to use curly braces to inject our variables right into the string. So let's change our text to read Response Percentage with a colon, and then we can add a pair of opening and closing curly braces and pass in the responseRate variable. C# will automatically pull the value out of that variable when the string is printed out because of that dollar sign prefix. Next, let's copy this line of code and paste it on the next line, and then we just have to change our text to say Unanswered Surveys and change our variable to unansweredCount. So now we have six statements. The first two are declaration statements to declare new variables. The next two are expression statements to calculate and assign values to those variables. And finally, these last two statements are also expression statements that print the value stored in our variables. We will discuss expression statements more later, but they are the most common type of statement you'll write in C#, especially for simpler single line statements like these. Once our statements are set up, let's run the app and see what happens. Visual Studio will take a moment to compile and launch our program, and then after a moment we should see the decimal percentage of people who completed the survey and the number of people who did not respond.
I do want to mention that this code is intentionally verbose to clearly visualize different types of statements, and it could be optimized in several ways. For example, we could perform our
addition at the same time that we initialize our variables. We could cut our statements that calculate values and just combine them up here with our declaration statements to do all this in one step, like so.
This style is closer to what you'll actually see in most C# apps, and it's just more succinct. So we're off to a good start. We've used a few simple statements to retrieve, store, and display some basic data. Next, let's start to explore the structure of statements like these in a little more depth.
Chapter 4 How to Explore Statements and Expressions
Let's start to examine statements with a bit more technical mindset to understand their structure better. Earlier, we looked at this conceptual definition of statements. Now let's expand on this a bit into more specific C# language terms. Statements are complete instructions that are written using a combination of C# keywords and expressions. You've already worked with C# keywords. These are reserved words of the C# language itself, and they have special meanings, like public, int, bool or new. Well, some specific types of statements also have their own reserved keywords, like the if/else selection statements that we'll explore more later. For now, let's focus on the expressions part of our definition, which are the core building blocks of statements. An expression is simply a sequence of operands and operators. This definition sounds a lot scarier than it is. Expressions are really just small fragments of code that generally result in a value and are much easier to understand if we look at some examples. As a simple example, 2 x 3 is an expression that resolves to 6. In this case, 2 and 3 are called the operands and the multiplication sign is the operator. HighScore +1 is an expression because it has two operands, the variable highScore and the number 1, and an addition operator. New Survey is also an expression because new is an operator and the Survey type is the operand. Even just the number 5 by itself is technically an expression because a simple number is an operand and results in a value. So, an operator simply applies some type of operation to an operand to produce a result. You can loosely think of these like nouns and verbs where the
operand is a noun and the operator is a verb acting upon that noun.
There are dozens of operators in C# that we can use to build statements, and a handful of them are listed right here. An operator can include anything from mathematical symbols for addition to greater than or less than comparisons or logical operators to determine if a condition is true or false. If we take a step back, it is important to understand that expressions are not statements by themselves. Statements utilize expressions, but expressions cannot stand on their own as complete instructions. This distinction is very important, so let's compare some expressions and statements. 2 + 2 is an expression, but we can't just write 2 + 2 all by itself in a C# program without any context. The app won't even compile this code. An expression statement would utilize this expression, but store the result in a variable and mark the end with our trusty semicolon to form a complete instruction. Evaluating true or false Boolean values is also a common use of expressions, but a complete statement again requires assignment in a semicolon. Some expressions can mostly stand on their own, such as creating a new object or executing a method, but they still require a semicolon at the end to form a complete statement. This book is certainly not a deep dive
into expressions. For our goals, we simply need to understand a few key concepts in the context of statements. Statements are composed of C# keywords and expressions and end in a semicolon or curly brackets. Expressions are a combination of operators and operands that generally result in a value. Operators act upon operands to produce that value. Expressions will also become more important when we start to work with other types of statements. For now though, let's practice writing different types of simple expression statements. Along the way, if you just keep these concepts in mind and start to recognize some general patterns in our code, you'll be in a great spot moving forward.
Let's expand on our earlier work with C# statements to experiment with some different expressions. Inside of our main method, our first two statements both include obvious uses of expressions. For example, the first statement uses the division operator to act on these two operands in order to compute the average. The second statement uses the subtraction operator to again act on two values, or operands, to compute the difference.
The equals operator is also being used to assign those computed values to variables. Well, let's switch over to our survey data to explore another scenario where we could use expression statements to accomplish a task.
Let's say the marketing department would like to know the overall average score across all four main categories. Right now, we only have individual scores, and it would be nice to have an overall average to track. So, let's tab back over to the main method in Program.cs. Below our existing calculations, let's start out by declaring another variable. This variable will also need to store a decimal value, so let's use the double data type again and call this overallScore. To compute the overall score, we must add all four individual scores and then divide those by the total number of categories, just like we would with any other standard math average. So in front of the semicolon for overallScore, let's start to access our different categories. So let's type Q1Results.ServiceScore and then use the plus operator with Q1.CoffeeScore plus Q1.FoodScore, and finally Q1.PriceScore. We also have to divide the total of these items by 4 in order to get our average. C# obeys the mathematical order of operations, just like any other programming language. So in this case, the division will actually happen first before all of the other scores are added together. That will certainly not give us what we want. We can fix this by simply wrapping our additions in a set of parentheses, and that will ensure that our scores are added before the division happens. Finally, let's copy one of our Console.WriteLine statements and paste that at the bottom. We can change the label to say Overall Score and then pass in the matching variable. With all that in place, let's run the app and see what this gives us.
We should quickly see the output for all three statement calculations printed in our console. This gives us a good idea of how to work with simple statements and expressions; however, there are some larger concepts at work here as well.
What we've done so far is created three different statements that produce calculated values. In fact, at the top of our statements here, I'll just add in a comment that groups these as calculated values. As we'll see throughout the book, arithmetic expressions like these can impact program flow in a really big way by helping us make decisions or triggering branching paths and so on. For example, maybe if the response rate of our survey is above a certain percentage, we take one action versus another. Next, let's explore statements that use other types of expressions and build on these concepts.
Chapter 5 How to Make Logical Comparisons with Statements
Let's look at how to write statements that perform different types of logical comparisons. So let's say the marketing team sent over some additional questions for us about the survey data. They would like to know if the coffee quality score was higher than the food score since that's really the heart of the business. They would also like to know whether customers are likely to recommend the Company to their friends and family. Finally, the director personally would like to know if the least favorite product is granola and the favorite product is cappuccino since those are her personal preferences as well. You may have noticed that all of these questions involve comparisons, and you'd be right. Comparisons can be done using expressions, and they're critical building blocks of program flow and statements. For this chapter, I have stubbed out a bit of starting code for us below our previous work.
We simply have a few variables declared to highlight the task objectives and a few lines to print out the results of those variables. Visual Studio will complain at the moment that our variables have no value using these red squiggly lines, but that's okay for us at the moment. By now, we already know how to write these types of statements, so let's focus instead on writing statements with new
types of expressions. So first, let's check if the coffee score is higher than the food score.
For this, we can simply type an equal sign out and then Q1Results.CoffeeScore and then use the greater than comparison operator and add Q1Results.FoodScore on the other side. The greater than operator ensures this expression will evaluate to true or false depending on if the coffee score was indeed higher. Comparison operators are a powerful tool for influencing program flow, as we'll see later. Next, let's check if the customers are likely to recommend the Company to their friends. We're going to say that a score of 7 or higher means yes since it's ranked out of 10. For this, we can use another familiar type of comparison operator called the greater than or equal to. So for this statement, let's add an equals after our WouldRecommend variable and then say Q1Results.RecommendScore is greater than or equal to 7. Finally, let's check the least and most popular products. So after our third variable, let's access the Q1Results.LeastFavorite product and then add the double equals operator, which performs comparison and not assignment like our single equals operator, and then just type out a string of Granola. We also want to verify that the most popular product is cappuccino. To check that two conditions are true, we can use the double ampersand sign, which represents and logic. After that, we can type out another comparison, so Q1Results.FavoriteProduct == Cappuccino. Now we have a statement that performs multiple comparison checks at once. So if both sides of an and operator are true, the statement
will assign a true value to our variable. However, if one or both sides of an and comparison are false, the statement will assign a value of false to the variable. We already have our Console.WriteLine statements defined at the bottom of our method that print out these variables, so let's just run the app and see what happens. After a moment, we should see our complete result set, including our new comparison values down at the bottom.
All of these variables hold true or false values since the expressions we used evaluate to Booleans instead of numbers like our previous work. Back in our code, I want to reiterate that the statements and expressions we have written here are another core building block for program flow. Later, we'll see how true and false computations can influence flow in a big way. So just like we labeled this first set of statements as calculated values, we can also label the second set as logical comparisons. Together, these are two essential building blocks for manipulating program flow, as we'll see in a moment. As a side note, I also do want to mention that there are better ways to check for equality between strings and C# than this simplistic approach. I have chosen this approach because it more clearly depicts different types of operators and expressions. Next, let's revisit our work thus far more specifically in the context of program flow.
We have spent quite a bit of time working with statements in the last several chapters. As we start to transition into the next module, let's return our focus specifically to program flow. So, let's make sure we understand the current flow of our app statements and how that
will evolve ahead. We now have a dozen or so statements that execute in order from top to bottom without any variability. Our program flow is entirely sequential, and we can actually prove that using the Visual Studio debugging tools. I am going to assume you have at least basic familiarity with debugging in an IDE like Visual Studio. As a quick reminder, the debugger allows us to step through our code one line at a time as it executes. It also lets us inspect the state of variables and other context data along the way. We'll be using this tool throughout the book to walk through our program flow, so let's place a breakpoint to the left of line 10, which will tell the debugger where to initially pause so we can inspect our code.
Next, let's run our app, and after this launches, we should land on the line of code where we set that point. This yellow highlighted bar indicates the next line of code that will be executed in our program flow.
At the top of Visual Studio, we use our debugging controls to step through our app one line at a time. So as I click the step over arrow, we will slowly move from one line to the next in order.
If we pause at line 15, at this point only the response rate has been written to our console. We can confirm that by checking our app window real quick. The response percentage is indeed printed out, but the other values are not displayed yet. This reassures us that these lines are written out as they are sequentially processed by our app. Back on our debugging controls, let's continue to step through this one line at a time until there are no more lines to process. We can then double check our app window again to see if the rest of our results have been printed out, and of course there they are.
So this is perhaps the simplest possible example of program flow. Our code simply executes one line after the other in order until there are no lines left to process. These are also all single line statements, so every line of code we process also represents one statement. Over the next few chapters, we'll explore special types of multiline block statements that are specifically designed to influence this program flow. This will allow us to create much more interesting flows than what we've observed here. As we move further into the book, it's important that we understand a few key concepts. Program flow represents the order in which our code or statements are executed. Simple programs often execute code sequentially from start to finish while complex programs contain branching paths based on various conditions. Statements are the smallest complete instructions for our applications. They are simple building blocks we can combine in all sorts of ways to accomplish complex tasks. Statements can
also influence program flow and the order or conditions in which other statements run. These statements are written using a combination of C# keywords and expressions. Some special types of statements even have their own reserved keywords, such as if and else. Expressions are composed of operators and operands, such as when two variables are added together. The variables would be the operands and the addition sign would be the operator that acts on them. Statements can be used to control program flow directly or indirectly. Single line statements often influence program flow more indirectly by calculating values and making logical comparisons. Multiline statements allow us to manipulate program flow more directly, so let's look at those more closely in the next chapter, starting with selection statements.
Chapter 6 How to Explore Selection Statements
At this point, we've written some simple statements and we understand their relationship to program flow. Now let's start to explore special types of statements that can control program flow more directly, starting with selection statements. Selection statements are the most common type of statement you'll use to control program flow. These statements allow you to execute different branches of code depending on specific conditions. There are two types of selection statements in C#. The first are if/else statements, which can select blocks of code to execute based on the result of a Boolean expression. The second are switch statements, and these execute sections of code based on pattern matches to a given expression. If/else statements are slightly more straightforward, so let's discuss those first. The if/else statement is the most essential tool in C# for making decisions.
Before, we discussed that statements are written using keywords and expressions, and selection statements are no different. This statement begins with the special if keyword followed by an expression that's wrapped in parentheses. This expression must evaluate to a Boolean
true or false value. We then define a block of code that will execute if that expression is true. Remember, multiline statements like this can define the start and end of blocks of code using opening and closing curly braces. If the result is false, the code inside of our curly braces is never executed and the program flow moves on. Previously, we examine different types of expressions that calculate values or result in true or false Booleans.
Well, now we can see how these expressions really come into play with selection statements and program flow. If statements can use
comparison expressions to decide whether to execute a block of code. They can also use equality checks to decide since these expressions also result in a Boolean value. They can even calculate values using expressions in order to make more specific decisions. If statements can also optionally be paired with an else block.
The else block will only execute when the first if expression evaluates to false. You can think of this as an either/or type of decision making. In this case, the first if block will only execute if the value stored inside of the product category is coffee; otherwise, the statements inside of the else block will execute instead. We can also add additional branches of logic to these statements by using the else/if syntax.
So in this example, if the product category is coffee, the first block will execute and the blocks below it will be skipped by program flow. If the product category is not coffee, the else/if expression will be evaluated instead. If the category is food, that second block will execute and the else block at the bottom will be skipped again. However, if the product category is not coffee or food, this final else block will execute as a fallback regardless of what the category value is. Let's look at an example of how to work with these types of statements in our code project. Let's see how we can start to use
selection statements to control program flow in our app. We created various expressions to calculate interesting data points or answer certain questions. Rather than just computing and printing out these values to the console, let's start to rework this code to actually make decisions. Let's say the marketing team would like to create a list of tasks for their staff to work on based on some of these values and comparisons.
So to start with, we no longer need these lines of code that simply write out values to the console, so let's delete those entirely.
Next, although all of these variables provide useful examples of expressions, we don't need this many of them to build out a few
selection statements. Let's delete the bottom two, and that will leave us with one comparison example to still work with. And then in our set of calculated values, let's delete the unansweredCount calculation. This leaves us with three of the more interesting expression statements to work with as a starting point for our selection statements. The next thing we need is a place to store the list of tasks that we'll create for the marketing department to investigate. One simple option for this in C# is to use the list type. So at the very top of our main method, let's type var tasks = new List, and then specify a string and angle brackets followed by parentheses. We can also hover over our list to pull in the right namespace. As a reminder, namespaces are essentially just containers that .NET types live in, so sometimes we have to add new references to them when we start to use additional types from the framework. If you're still new to C# or coding in general and don't understand this line yet, that's totally fine for this book.
A list is simply a type that allows us to store multiple items inside of it. We specify what kind of data it should hold using these angle brackets, which for us will simply be strings that represent new tasks for the marketing team. The new keyword actually creates the list for
us to work with and then it's assigned to our tasks variable for use throughout our app. Next, let's write the statements that will create tasks. So, let's assume that if the coffee score is lower than the food score, the marketing team wants a task to investigate the recipes and quality of ingredients. We can accomplish this using a simple if statement. At the bottom, we have already calculated the answer to this question using a Boolean, so let's leverage that to make our decision. A couple lines down, let's write out if (isCoffeeScoreLower) and then stub out a block of code using our curly braces. Inside the block, let's say tasks.add, which will use the list we created earlier, and pass in a string that says something like Investigate coffee recipes and ingredient quality.
Because the isCoffeeScoreLower variable already holds a Boolean true or false value, that's actually all we have to do. If the score is lower, a task will be added to look into this. If it's not, our program flow will skip this logic and move on. This also highlights an important point about selection statements. We can either use variables like this in our decision or we could put the comparison expression directly inside of our parentheses. Just as an example, I could copy this expression up here that handles our comparison and then paste that
directly inside of our if statement parentheses. And, that means we could actually delete this line further up.
Either of these options have the same effect, so it really just comes down to readability and whether you need to reuse the same value in multiple places. We do not need to reuse this comparison anywhere, so this approach works fine without our variable. Now let's use our debugging tools to verify this code's behavior and the impact that it has on program flow. So let's place a breakpoint next to our if statement and then click Run.
As expected, we'll end on that breakpoint. When we mouse over our CoffeeScore and FoodScore, we can see the CoffeeScore is actually
the higher of the two, so if I were to click Step Over to walk through this code, we actually jump right over the line that would add a new task and to the end of the method. And if we mouse over our task variable, we can see the count is still 0. Our if expression evaluated to false, so this block of code was not executed. Interestingly, this is the first time we have ever run our app where some lines of code did not execute. We have successfully altered the program's flow. Let's stop our app, and just real quick, if you want to see the inverse of the scenario, you can do that by changing the values in the Q1Results file. So, let's tab over there a moment.
For the CoffeeScore, you can just change this to something below the FoodScore, like 4 or 5, and then click Run again. Visual Studio will jump back over to our breakpoint, and now when we step through this using the debugger, this time we actually do step down inside of our if block of code and a task is added to our list.
Let's step forward another couple times to make sure that line executes, and then if we mouse over our list of tasks again, Visual Studio will confirm for us that the string was added.
Next, let's look at an example of branching paths with if/else statements.
Chapter 7 How to Create Branching Selection Statement
Let's explore how to use if/else statements to handle branching decision logic. So let's say the marketing team would like to use this overall score rating to make some decisions. If the overall score is above an 8, they want to reach out to other leadership and figure out how to reward employees across the company.
Otherwise, if the score is below 8, they want to connect with employees for ideas of how to boost this score. Below our current if statement, let's move down a couple lines so we can add another selection statement. So let's again say if, and for the expression we can say overallScore > 8 and create our nested code block.
There, we can add another task that says Work with leadership to reward staff, or something along those lines. I also want to point out how much more readable this conditional is by using a variable than if we were to pack all of the logic from above where the score is calculated into the if expression itself. So, that covers our first scenario, but what if the score isn't over 8? Well, that's where we can use an else block to cover the other scenarios or values. So let's use the else keyword and then stub out a second code block, and inside of that we can add one more task which just mentions something like Work with employees for improvement ideas. And that completes our first full if/else statement. Keep in mind that else blocks are a pretty broad catchall. They handle every scenario except the one we outlined in the if, so use caution when applying them. Let's set a breakpoint on our if and then start up the app. After a moment, we should break on the line that we specified, and when we mouse over the calculated score, we can see that it is not greater than 8. So as we step through this, program flow will skip past the first if and instead land in the else block, which handles all other scenarios. The else will still add a new task to our list, and then the app can finish running. If you are interested, feel free to tweak the values in our Q1Results class to try and trigger that if
block scenario. Next, let's start to put all of these concepts together for a slightly more involved scenario.
Let's work through a slightly more involved task using an if else statement with multiple conditions. So let's say the marketing team would like to leverage this response rate value, which represents the percentage of people who responded to the survey. They would like to use this percentage to decide next steps and how to use their remaining survey budget. If less than 33% of people responded to the survey, they would like a new task to research ways to improve that poor response rate. If of members responded, they would instead like to create a task to send those engaged members a reward coupon for a free coffee. Finally, if over 66% of customers responded, they just want to send them a discount coupon since this would be too many people to give away free coffee to. Below our previous code, let's create a new if statement and stub out an empty block of code.
For our if expression, let's check if our response rate is below .33 or 33%. Then in our code block, let's say tasks.add and something like research options to improve the response rate. After our closing curly brace, let's then add an else if scenario using those special keywords. For our expression, let's say response rate is greater than .33 and response rate is less than .66. This else if scenario is a good example of how variables can let us reuse a previous calculation and keep new calculations simpler and more readable. This combined and logic would be fairly verbose without the use of a variable. Let's also make sure to add a new task in here that says something like reward respondents with a free coffee to match our directions for marketing. Finally, at the end, let's add a simple else block, and inside of that, add one more task that reads rewards respondents with a discount coupon or something like that. We do not need to specify any expression for this else because if the code reaches this point, the only possible response rate would be above .66. Next, let's add a breakpoint on the start of this new if else statement and then run the app to see what happens. After we hit our first breakpoint, we can mouse over our calculated response rate which shows a value of .65.
This means that as we step through our conditional, the first if block will be skipped since that checks a response rate of less than .33. However, if we continue to step forward, we will land inside of our else if block and a task will be added to send participants free coffee. That also means that if we continue to step forward, our final else block will be skipped. Remember, the final else only executes if all of the conditions above it evaluate to false and that's not what happened. So instead, we just moved down to the end of our main method. So that gives us an idea of how to work with selection statements that include multiple expressions and conditions. For additional practice, I encourage you to experiment here with the values in the Q1 results survey data file and see if you can get some of these other conditions to trigger using different data. Next, let's start to explore an alternative to if else statements to handle a different type of scenario.
Chapter 8 How to Explore Switch Statements
Switch statements are another type of statement that can control program flow. Although if else statements are more commonly used, switch statements provide a powerful alternative in certain situations. Switch statements allow us to execute sections of code based on expression pattern matching. This sounds a bit more abstract or complicated than it is so let's look at a few examples. Here, we have a standard switch statement.
At the start of the statement, we use the switch keyword and then define an expression in parentheses. In this example, our switch expression will evaluate to whatever string is stored in the product category variable. That string value is then compared to each case pattern below it, and if a match is found, the associated block of code will execute. So if the product category is food, only the statements in the second case section will execute and the cases above and below it, those will be ignored. We also must include the
break keyword inside of our switch sections. This let's C# know that it can exit the switch statement and resume normal program flow.
It is important to understand that a switch expression can evaluate to any value such as a string, number, and other C# types. This is very different than an if else statement where expressions must evaluate to a Boolean true or false.
So even though these two different examples accomplish the same thing, the if else statement must explicitly convert each check to this Boolean comparison that we see. So switch statements are really useful when you have many different scenarios to compare against a specific value or expression. For example, if we want to run different logic for every day of the week, a switch statement provides a concise alternative to an if else statement. If you find yourself writing more than three or four if else branches, consider whether you might be able to use a switch statement instead. Let's take a closer look at the case labels in a switch statement. These labels are defined using the case keyword followed by the pattern that is matched against our top level switch expression.
The pattern is usually a constant, such as an int or a string value and other simple types that we've worked with. C# will enforce that the type you enter as your case pattern can be compared to whatever the switch expression evaluates to. So in this example, we could not set a Boolean as our case pattern since our switch expression evaluates to a string for the day of the week. I also want to mention that starting in C# 7 and higher, we can also utilize types to create more involved pattern matching. However, this is more of an advanced topic that requires a strong understanding of the type system, so we will not be covering that scenario in this book, but it is good to be aware of as you move forward in your learning. Switch statements can also define a default case and that will execute if the match expression does not match any of the other cases. The default case is sort of similar to the final else block in an if else statement that we saw earlier, it simply acts as a fallback section if nothing else matches. One other useful feature of switch statements is the ability to stack multiple case values on top of one another, this prevents us from having to duplicate code for multiple values that should all result in the same logic. For example, let's say
we have a price score that ranges from or bad to good. We could stack some of these values to group general sentiment and not have to write out a case for every single possible value. Let's try implementing some of these concepts next. Let's use a switch statement to complete another task in our application. For this example, let's briefly revisit the customer survey form and remember, this is simply a conceptual representation of the survey that would be emailed to customers so we can visualize certain concepts, it does not actually connect to our C# code. So let's say the marketing team wants to focus on this question that asks the customer what area Wired Brain could improve on the most. The includes a variety of options to pick from, such as pricing, rewards, etc. Depending on which item customers picked the most, the marketing team would like to take different actions.
Our results data has aggregated the choice users selected the most so we can take action on that result. Well over in Visual Studio, let's revisit our main method. The area to improve data point is a good use case for a switch statement because there are so many different actions we want to take based on the value of a single item.
So at the bottom of our code, let's start out by typing our switch keyword and then in the parentheses that follow, let's access our Q1Results.AreaToImprove. This means that all of our nested case values will compare against the values stored in this AreaToImprove property. So let's stub out our curly braces here and then start to add in a few case patterns. First, let's say case RewardsProgram: with a colon afterwards and then move down to the next line. Here, let's add a task that says something like Revisit the rewards deals. Let's also remember to add the break keyword on the next line after that statement. Break tells our C# program flow to exit out of the switch statement once the matched case has been executed. Next, let's add another case pattern here for cleanliness, and below that, just add another task, maybe something along the lines of contact the cleaning vendor about the current process. Let's also add a 3rd case for mobile app. And below that, let's put in one more to do to contact the consulting shop that built the native app with some concerns or ideas. Finally, let's say that for all of the other options in that dropdown list, the marketing team doesn't know what they
want to do yet. So at the very bottom of our switch statement, let's include a default case, and below that, add a task to investigate the response comments for ideas. Remember, users can also submit miscellaneous comments on that survey so later in the book, we'll see how we can start to analyze those individual responses for situations like this rather than always working with our aggregated or overall scores. So that completes our switch statement for now. Let's place a breakpoint next to the first line of this statement and then click to start our app. This time, our debugger will land on the switch statement, and when we mouse over our area to improve, we can see that it's currently set to the mobile app. This means that it was the most common item users picked from the drop down for an area to improve. It also means that the third and only the third case should execute.
So let's step further into our code with the debugging tools and we'll see that that's exactly what happens. And if we were to keep stepping forward, we'll hit our breakpoint and jump out of the switch statement and back into our standard program flow. So switch statements are a great option whenever we need to select a section of code to execute based on a matching expression. Next, let's see how we can restructure our app a bit to support more statements as it continues to grow. Before we move any further into our exploration of statements, let's see how to improve the existing setup of our app for future development. At this point, we've actually written quite a few statements inside of our main method. If we continue down this exact path for the rest of the book, the main method will become very large and difficult to manage. Although Main will always continue to act as the entry point into our app, we can start to
create additional methods that extract our logic out into more manageable pieces. A method is a reusable block of statements that can optionally accept inputs and return our output values. So to improve this setup, we can extract our statements out into other reusable methods beyond just main. Each of these methods could then be executed to create different types of reports for the marketing team or whoever wants to run them, which we'll get more into later. For example, all of the statements currently in our main method could be grouped into one method that creates a report of tasks to work on. Visual Studio offers some quick actions that can help us refactor this code into a separate method very easily.
To start with, let's simply highlight all of these statements inside of our app like so and then to bring up a context menu. Here, let's select the quick actions option at the top and then choose Extract
method. This will automatically move our code down into a method with all of the essentials stubbed out for us.
Let's rename this GenerateTasksReport and then hit Enter. Let's also change the private access modifier of this method to say public. Now, any time we want to run our set of statements that generates a task report, we can simply call or execute this method by name. Visual Studio has even taken care of that part for us.
Inside of the main method, we can see a call to our GenerateTasksReport, which includes the name of the method plus a
set of parentheses. Our method does not accept any inputs or arguments, so the parentheses are empty. Let's place a breakpoint in our main method and then one inside of our GenerateTasksReport method and then see what happens when we run the app. Well, first, we land inside of the main method since that will continue to be the entry point of our application, that functionality is just built into .NET. However, as we step through our code, our new method will execute by name and we'll jump down into that block. Those statements will then execute like usual as they have throughout the book.
If you are unsure of what these public static void keywords do, that's totally fine for the scope of this book. In simplified terms, public and static basically allow this method to be easily accessible by other components throughout our app. Void states that this app does not return or output any values to the code that called it. And of course, GenerateTaskReport is the name of the method and it's how we execute it from other places in our app like we are in the main method. Together, this line of code collectively forms the method's
signature which essentially describes the methods, inputs, outputs, and accessibility. All methods must include a signature that roughly follows this structure. As we progress further into the book, we'll add additional types of reports to our app and learn how to control program flow across different methods. Next, we'll be ready to look at new types of statements later in the book. Selection statements are essential building blocks of C# applications and program flow. These statements allow us to choose sections of code to execute based on specific conditions. If/else statements are a type of selection statement that execute different blocks of code based on the results of Boolean expressions. We can use a combination of if/else and else/if keywords to create branching paths throughout our app. Switch statements are another type of selection statement. They can select a block of code to execute based on different case patterns that are matched against a switch expression. Switch statements are a great option when we need to compare many possible values against a single expression. Next, let's start to explore new types of statements that can influence our program flow.
Chapter 9 Understanding Iteration Statements
As we start to build more complex applications, we quickly encounter situations where we'd like to repeat the same task many times. Well, luckily for us, computers are extremely good at doing the same thing over and over very fast. So in this chapter, we're going to explore how to do just that in C#. At the start of this book, we explored Bob's routine and how he does pretty much the same thing every day of the week. He wakes up, goes to work, makes dinner, and then there is a slight variance in how he enjoys his evening. This routine, or loop, continues every day until the weekend, and then the routine changes. If someone asks us what Bob's routine is during the week, we would not repeat the same list of tasks five times. Instead, we would say something like, during the week Bob performs these tasks, but then he does something else on the weekend. We call this routine a loop, and the loop does not stop or change until some specific condition changes, such as the start of the weekend. This same concept can be found everywhere in our applications. Many times we'll want our program to repeat some task numerous times. Perhaps we want to loop through all of the comments on a website, or maybe we have to loop through all the products in a shopping cart, or perhaps we have to iterate through a list of contacts to send them an email or a text. Attempting to handle these types of tasks manually one line at a time would be tedious in some situations and simply impossible in others.
Even with all of the code we see here, we'd still only email a handful of customers and reach thousands of people. This simply would not work. We need a better way to process large sets of data and rapidly perform the same tasks over and over. C# allows us to use iteration statements to accomplish this. Iteration statements, or loops, as they're generally called, enable us to execute a block of code over and over until a certain condition is met. Or in other words, they iterate over a set of data or statements to achieve a result. There are several types of loops, but the most standard example is called a for loop. The for statement executes a block of code repeatedly as long as some condition evaluates to true. Later in
the book, we'll look at some other types of loops that achieve this same functionality in slightly different ways. For now though, let's start with the most classic for loop example.
So, let's say we want to generate 100 discount codes using a loop. We begin the statement with the for keyword. Then, in a set of parentheses, we define three sections that control the behavior of the loop. First, we have the initializer which only runs once at the very beginning of the statement and usually creates a local variable to use for counting the iterations. The next section defines a condition, and this is evaluated every time the loop is about to run. As long as the condition evaluates to true, the loop will go ahead and run. This condition generally uses the number we initialized in the first section, such as checking if it's less than 100, in this case. Finally, we have an iterator section, which increases that initialized number each time the loop runs. The for loop also defines a code block that will run for each iteration of the loop. In this case, once the variable reaches 100, the condition will be false and the program flow will move beyond the loop. So just as another example, let's reconstruct Bob's weekday routine as a loop, assuming the week starts on a Monday.
That would make days So, first we initialize an integer as 1 to represent Monday. Then we check if that integer is less than 6 to ensure we have not reached the weekend or Saturday yet. We also increment the i variable by one every time the day's activities repeat. So after they run 5 times, i will become 6, which means that the loop condition will evaluate to false and the loop will stop. Let's see how to apply these types of concepts to our own app next.
Let's explore how to use a standard for loop to process a set of data in our app. For this chapter, there are some changes to our Q1Results dataset, so let's revisit that file for a moment. Now up until this point, we have worked with aggregated scores and data. In other words, these were the total average scores for all of the people who responded to the survey. However, this type of data is not all that useful when working with loops and iterations.
Loops are designed to repeat blocks of code and process many items in a set of data. So further down in this file, we have a new set of data to work with. Our result set now includes all of the individual responses to the survey in a list. Remember, a list is just a C# type that can hold many different data items in it, like we saw earlier with our tasks. Each individual survey result has almost the same data points as our aggregate scores, but on that individual customer level. That means we also get their email address, which we can use for follow up. We also have individual comments from them that we can use to discover more specific or other feedback insights. So for our first example, let's say the company would like a list or report of comments from all of the people who were not likely to recommend the coffee shop to friends and family. At the moment, we only have
a handful of responses here, but you can imagine how difficult this might be to manually sort through if you had hundreds or even thousands of responses. Well, this is the type of situation where loops become invaluable, so let's see how to handle this objective over in the main method. Below our call to GenerateTasksReport, let's start to work with a new type of report using a for loop.
So let's add that for keyword, and then open up a set of parentheses and the associated code block that will run below it. Then inside of our parentheses, we have to set up those three sections that we looked at. First, let's create an initializer, which is commonly called i for increment or index. And then let's set a conditional to evaluate for whether or not to actually run the loop. For this, we can say as long as i is less than, well, less than what? We do not want to just put an arbitrary number in here. We actually want this to be the number of responses in our dataset. That way our loop will keep iterating over the responses until it runs out of items to process. An easy way to get that number of responses is to say Q1Results.Responses.Count. Count is a very useful property on
the list type that easily gives us the number of items it holds. Then after our conditional, we can simply type i++. This tiny bit of code will increase i by one every time after the loop runs, and then i will be checked again in our conditional expression to see if it should run again. Inside our loop code block, let's think about what we want to do here now. Essentially, we want to get the current item or survey response from the list that we're iterating over and then grab the comment off of it, but only if the WouldRecommend score is less than seven. So first, let's get the current item and assign it to a variable to work with. We can say var currentResponse = Q1Results.Responses. And then right after that, we can use a set of square brackets and pass in the number i. Square brackets are a type of dictionary syntax in C# that allow us to grab items off of a list or a collection using an index, or this variable number in other words. In this case, the i iterator variable will work just fine since we can use it to reference the current item in the list that we're stepping through. On the next line, let's use a conditional to check if the current item's recommendation score is less than seven, and then create another code block. This is a great example of how we can actually combine different types of statements to gain even finer control over our program flow. We are essentially using a selection statement to filter the work of an iteration statement. Finally, inside of the if block, for now we can simply use a Console.WriteLine and pass in the currentResponse.Comments. That way it will be displayed in our app window when it runs. And that completes our for loop. This code will print out all of the individual comments in our survey responses from customers who might have concerns. Now, for loops are actually really interesting to step through with the debugger, so let's place a breakpoint at the start of the loop and then start the app and check out how this works. First we'll land on the start of our loop, just like we expected, but notice that the entire line is not
highlighted yellow like usual. Instead, only this initializer section of our loop is highlighted. Remember, the initializer only runs once at the very start of the loop to create and assign this variable. When we click Next again, we move over to the condition expression. C# will always check if this condition is still true every time before the loop runs.
We have more than 0 comments in our data, so this will result to true. So let's step through down into our code block. That code will execute, and afterwards we'll land on the incrementor section back at the top. This runs every time after the loop finishes to increment our variable so we can continue moving through our items.
So if we continue to step through this, that condition will be evaluated again, and then the code block will run again. This pattern will continue for all of our response items until we reach the end of the list. Notice how sometimes we fall in our if block and sometimes we don't, depending on what that recommendation score is. Eventually, we'll reach the end of our dataset and our app logic will finish. If we check our console, we can see that several comments have been printed out. All of these comments contain suggestions for improvements from those unhappy customers. Although the setup works fine, C# provides other options for working with loops, which in some scenarios can greatly simplify this process. Let's look at those other types of loops next.
Chapter 10 How to Explore Different Types of Loops
Now that we have a working knowledge of loops, let's start to explore some variations or alternative types of these statements. I briefly mentioned that there are four types of loops. We already looked at the standard for loop, so now let's explore these other three. All of these options achieve basically the same thing, they repeatedly run a block of code, but each has advantages in specific situations.
First, let's look at the foreach loop, which is arguably the easiest type of loop to work with in C#. This option streamlines the for loop we looked at in the previous chapter using the special foreach keyword. Rather than having to initialize a variable and use specific incrementors, this type of loop takes care of all of that for us. We simply specify a name for the current item we are working with as we move through a collection. It then executes a block of code for each item in that collection. So in this example, we want to email a survey to every customer in the mailing list. One quirk to be aware
of with foreach loops is that we cannot modify the size of the original collection while we are looping through it. So if we start with 100 customers to loop through, we have to keep that same 100 set. Because the counting and incrementing is handled for us in the background by C#, it will not adapt to a change in the size of the collection. This is a fairly uncommon requirement in the first place, but it's good to be aware of. The next type of loop is called a while loop, and it comes with a matching keyword again.
While loops simply execute a block of code over and over, as long as some Boolean expression evaluates to true. These types of loops are really useful when you have no idea how many times the block of code must run. For example, let's say we have to read in a text file, and that contains an unknown number of rows of product data. We could use a while loop to check if we have reached the end of the file every time another row is processed. While loops are also often used in interfaces or games. So, for instance, while a controller button is pressed, a game character might continue to run forward or while an arrow key is held down, a graph will progressively draw more information. These are scenarios where the number of times the code has to execute is very dynamic, or constantly changing. The last type of loop is called a This type of loop is almost identical to a while loop, with one important distinction. A will always execute at least once, even if the condition being evaluated results to false,
because that condition check happens after the loop runs. With a standard while loop, that check happens before the loop runs.
This type of loop is useful in certain niche situations, but it's fairly rare to see in applications, so we will not spend a lot of time with it in this book. One other feature of loops to be aware of is the break keyword. You might remember this word from our switch statements. Break allows us to short circuit a loop and jump out of that statement and back into normal program flow, regardless of whether the loop condition has been reached or not.
So in this example, our customers would never be sent an email, since the code breaks out of it before that method below it is ever called. I also want to very quickly mention LINQ in case you may have heard of it. LINQ stands for language integrated query, and it provides some powerful options for filtering and iterating over data in C#. Some of our examples could be easily implemented through LINQ, however, LINQ is a sizable topic of its own and there are other C# book on this topic in detail, so we will not be discussing it in this more beginner level course. LINQ is my personal favorite feature of C#, though, so I encourage you to go and explore this great topic more on your own. For now though, let's start to explore how to use additional types of loops in our app.
Let's see how to use a foreach loop as a simple option for processing sets of data. In the previous module, we briefly explored this area to improve property on our set of data. This mobile app value is what the majority of users selected as the area could improve on the most. Well, in order to come up with actionable ideas, would like to find all of the comments from people who selected mobile app as their area to improve. So back in our Program file, let's see how to use a foreach loop to accomplish this. Let's step down a few lines below our for loop and then create a second loop using the foreach keyword. Inside of our parentheses we can simply type var response in Q1Results.Responses. Behind the scenes, C# will automatically iterate over every item in the responses list and assign the current iteration item to this response variable. Next, let's drop down into our code block, and in here we can add another nested if statement. This time we can say if response.AreaToImprove == Q1Results.AreaToImprove, and that will
filter our responses down to only those items that match the aggregate AreaToImprove score. Then in our nested code block, let's again write out those comments to the console.
For this example, I have intentionally chosen a scenario that is very similar to our previous loop so we can appreciate just how much simpler the foreach syntax is. We do not have to worry about managing incrementer variables or accessing dictionary items using an index or manually creating a CurrentItem variable for readability or any of those sorts of tasks. In C# apps, foreach loops are by far the most common type of loop you'll see because they are extremely easy to use for simply iterating over collections. Let's temporarily comment out this first loop, and this way we can debug our new one in isolation. You can do that using the Comment button at the top of Visual Studio or just using the keyboard shortcut Ctrl+K/Ctrl+C. Commented out code does not execute when the app runs. Then let's set a breakpoint at the start of our new loop and start the app.
After a moment, we'll pause on our new foreach loop. As we step through this code, we first land on our collection and then a local variable is set for the first item in that list. And as we continue to step through this, that response variable is continually reassigned to the next item in our list. Some of those responses match the area to improve that we're looking for, and those items will be written out to the console as we iterate through this. So just click through the remaining items or remove the breakpoint and hit Continue to jump ahead and see the final results printed in the app. So now we've seen how to streamline iterations using a foreach loop. In order to keep our code organized, let's package up these loops that find problematic comments into their own method, just like we did with our tasks report. So let's highlight these two loops and then and choose Actions, Extract method. Then, let's name this new method something like GenerateCommentsReport, and this will just help keep our main method more manageable as we continue.
In the next chapter, let's continue our work with loops, but let's see how to use a different type of loop to solve a different type of problem.
Chapter 11 Working with While Statements
Let's see how we can use a while loop to accomplish another objective of our app. So let's say that the marketing team would like to pick two people to give away a free gift card to. The drink of the month was a cappuccino, so they want to pick the first two responses who selected that as their favorite product. So essentially, we want to loop through our survey responses until we find the first two people who meet this criteria. This criteria is subtly different from our previous loops. We only want to iterate through our responses until some specific criteria is met, and then we can forget about the rest of the items. With our previous loops, we specifically wanted to process every single item. There are a variety of ways to accomplish this in C#, but let's see how to do this using a while loop. Above our existing code, let's create some spaces with a few new lines and then start to type out our new loop with the while keyword.
We also need our usual set of parentheses and a block of code below that. To pick our three winners using this loop, we're going to need to use two variables. The first is a simple list to hold the email addresses of the winners. So above our loop, let's create a variable called selectedEmails, and then assign an empty new list of type string to that. We also need a counter variable to keep track of what response item we're on as we move through that loop, so let's set up an integer and call this counter and set it to 0. This integer will be used in a similar way to the incrementor variable in our standard for loop. For our loop expression, let's say selectedEmails is less than two, since this will return false once we have two winners. And, let's also ensure that the counter is less than the Q1Results.Responses.count. This expression will then terminate our loop if two winners have been picked, or if we reach the end of our available responses, even if we haven't found two yet. This will give us the desired result while preventing errors. Inside of our curly braces, let's create a variable to hold the current item in the loop. That would be var currentItem = Q1Results.Responses with the counter passed in, similar to what we did with our for loop. Then let's add a simple conditional that says if currentItem.FavoriteProduct == Cappuccino, and then inside of the conditional, let's also add the currentItem.EmailAddress to that list of winners. If you'd like, you can also add a line of code below that to write out the response to the console, and that will make it easier to see the results, so I'm going to add that in here as well. We also need a line of code to actually increment the counter variable. Remember, this is not a for loop or foreach loop or that's handled directly for us; we have to manage that ourselves. At the very bottom of our code block then, let's just write counter++, and this is C# shorthand to increment a number by
one. Now every time our loop runs, it will access the next item in our results. Let's set a breakpoint on our while loop and then click to start up the application.
After a moment, we'll land at the start of our while loop. Our counter variable is set to 0, and there are no winners yet, so our expression should of course return true. And as we step down into our code block, it is worth pointing out that this is the first type of loop we've used where the entire starting line executes all at once, rather than in those sections. That's because a while loop simply checks if one expression is true, and if it is, it keeps running. So as we step through here a few times, our counter will increment manually, and that top line of code will keep executing as a whole. However, what's really interesting is that our loop will actually stop after only several runs. We stepped through this loop significantly fewer times than there are items in our list. Well, if we examine our console window, we can see that two winners were already found.
This means our while loop condition evaluated to false, and the loop simply stopped running. This illustrates how a while loop does not really care how many items there are in your dataset, or what variables you've initialized, or even what the current item is. It only cares if the expression you provided evaluates to true, and it uses that to decide whether to keep running or not. Next, let's start to wrap up the module by exploring the relationship between variables and the structure of these loops in a little more depth.
At this point, we've worked with several different types of statements that use blocks of code, so let's explore a few concepts or ideas to be aware of in terms of variable scope with these code blocks. We created this while loop, and this uses a few variables that we created ourselves. When we start to mix different types of variable statements and methods together, it becomes important to have a basic understanding of variable scope. So one concept to be aware of is that by default, variables are scoped to the current code block that they are created in and any nested code blocks.
For example, our monthly winners list is created at the root of our Main method. This means that any code inside of the Main method and any nested code blocks like our while statement are able to use that List variable. However, only code below where the variable is defined can use those items. So, for instance, if we were to cut our two variables out and then paste them below our while loop, Visual Studio will instantly complain to us that this is not acceptable. We see these red squiggly lines appear under parts of our code, and if we move these back to where they were, those warnings go away. As another example, we have this currentItem variable defined in our loop. This means that the nested if code block has access to that currentItem. However, below our loop, if we were to try and access that same currentItem, Visual Studio and C# would again complain to us. So if I were to start typing currentItem., we do not get our
IntelliSense suggestions, and if we manually type in one of its properties here, the whole line still shows as red. CurrentItem is only available inside the context of our loop. Speaking of loop context, it's also to be aware that any variables we create inside of our loop are recreated every time the loop runs, so we cannot put our monthly winners list or the counter variable inside of our loop, or they would be back to 0 with every iteration of the loop. That's why we create them outside the list and then increment them or add to them from inside. I also want to mention that these same concepts apply to our selection statements.
So further down in our GenerateTasksReport method, we create a new list of tasks at the very top of that code block. This means that the tasks list is available to use anywhere inside of the GenerateTasksReport method, including in nested if blocks. However,
the task list is not available in our Main method, since that is an entirely separate method with a scope of its own. Now if we were to take that list of tasks and cut it out and then paste it back inside one of our nested if blocks, right away we'll see those same types of issues and red lines. Because that list is now created inside of our first if block, it is unreachable in sibling or parent code blocks. Our other selection statements can no longer add tasks to it, so let's just undo that quick. So as you start to write more complex code, you'll want to think carefully about where you define your variables. Scope and data context can be a pretty involved topic, but this gives us a good starting point. Variables should always be created at the minimum required scope.
]
It's a bad practice to just put everything at the code block, since that can cause readability issues and produce unexpected bugs when too many pieces of code are modifying the same variables. We will start to explore more of these concerns or ideas in the next chapter. Before we get there, though, let's wrap up by reviewing a few key concepts. Let's review some small changes to our application that will organize it more effectively for the rest of the book. So up to this point, we have used a combination of console outputs and lists to manage the data items we are working with.
For the end state of this module, I have made a few small cleanup changes to our project to standardize these approaches across our
methods, so if we look through here carefully, each report will now output its results to the console along with a header description of that report. Each report will also store any relevant items in a list, such as a list of tasks or comments like we started earlier. That way when we run the reports, we can see the results output in our console, but we also have an list of those items to work with. This second point is important, because it allows us to do other things with our data. On each report, I have added a line of code at the bottom that says File.WriteAllLines, and then I provide a name for a file, as well as a list of items for that report. This line of code uses the C# file helper class to write this data out to a file on our file system. Working with files is not really a focus of this book, so we do not need to spend a lot of time on this code, except to understand the basics of what it does. This setup is valuable to us because it means that the outputs of our reports can be accessed outside of our app by other people. So, for example, someone could run this report and then take the generated text file and email it to someone else. The files are saved as CSVs, so they can be opened as rows easily in something like Excel. Later in the book, we'll see how to control which reports get generated based on user input.
So if I collapse all three of our reports, and you can do that with the Ctrl+M, O keyboard shortcut, we still have a clean Main method to work with that just runs all these reports in a row, but now we'll get a consistent output across all of them. Let's start our app at the top of Visual Studio to see what happens now. So after a moment we should see our data results output to the console, which is certainly helpful while we're working. We can see the tasks, improvement concepts, and selected rewards members that we set up in the earlier modules. However, our app now also outputs these results as files. Those files are located in our project directory on the file system. So over in our Solution Explorer, let's on our project node and say Open Folder in File Explorer, which is a useful shortcut. In the root directory here, let's navigate down through the bin folder and then all the way into our .net5.0 folder. This is the directory our app actually executes out of, which we can see with our
Reports.exe file here. However, we also now have three report files such as this TasksReport.csv. Let's to open that in Excel, and we'll discover the list of tasks that people should take action on from our app. These files could then be sent around and used however needed. We could also add more reports to our app in the future that output other files, and polish up the formats and data inside of them, and so on. So, again, managing file outputs is not a huge focus of our app, since we are more interested in the program flow to get to that point; however, this general setup provides a clean starting point for some of the concepts later in this book, and a good place for us to close out this module. Iteration statements are a powerful tool for controlling program flow. They allow us to repeat a block of code many times, often to iterate over a set of data. There are several different types of loops in C# that each provide different features to handle different situations or objectives. For loops are perhaps the most classic type of loop. They generally initialize an incrementer variable, which is used in a conditional check to repeat a block of code a given number of times. Foreach loops provide a streamlined alternative to for loops with a slightly reduced feature set, but much simpler syntax. While loops are a very flexible, more manual approach to loops. They allow us to repeatedly execute a block of code as long as a given expression evaluates to true. We also have the do while loop, which is a variation of the while loop that is guaranteed to execute at least once. Finally, it's important to understand the nuances of variable scope. At the most basic level, just remember that variables can be used inside of the code block that they are declared in, as well as any nested code blocks. They must also be declared above the code that utilizes them. In the next chapter, let's explore a few more advanced features of statements and additional application program flow considerations.
Chapter 12 How to Explore Additional Program Flows
Let's start to bring together many of the concepts of this book to achieve some larger goals. There are really two main areas we want to focus on to round out our discussion. First, we'll explore more ways to control program flow across our application as a whole. We've written a fair amount of code up to this point, so we want to take a step back and explore how to start managing this larger setup. Then later on, we'll wrap up the book by discussing a few more advanced features of selection statements and their relationship to program flow. These more advanced features can be very useful in specific situations. To expand on our first goal, most of our work with program flow in this book has centered around individual methods or specific statement examples, create this task, or print out this message, and other tasks. As we start to build more substantial apps, we must understand how to control program flow between larger pieces of our apps and in a wider variety of situations. We want to influence program flow at the macro level across multiple methods and code files or classes. We also want our program flow to adapt to user input and more dynamic data. Let's start to explore how to accomplish some of these goals next.
Controlling Flow through User Input
Let's see how to make our app more interactive by collecting user input and using that to influence program flow. Back in Visual Studio, right now in the Main method we are simply calling our three reporting methods sequentially.
Although these methods have branching logic inside of them, at the application level they still just execute one after the other. Well, what if we wanted to make this more dynamic? What if we wanted the user of the app to be able to choose which report will run? This is really easy to do in a C# console app using a basic selection statement. So at the top of our Main method, let's add a couple statements to capture the user input and store it in a variable.
First, let's inform the user that they actually can select a report in the first place, so we can say something like Console.WriteLine, Please specify a report to run, and then in parentheses just give
them some options that loosely map to our methods such as rewards, comments, and tasks. Then on the next line let's say var selectedReport = Console.ReadLine. ReadLine prompts the user to type something in and captures their input, versus WriteLine, which just prints something out to them. ReadLine is built into the .NET console app itself, so that's actually all we have to do to capture their input when they hit Enter. Next, we can use a switch statement to actually run a report based on whatever they pick. A switch statement is a good option for this scenario, because we are matching many possible values against one expression. As our app grows, there could be a huge number of reports we want to run, so an if/else type conditional would become very verbose compared to our switch statement. I'm just going to paste in the completed switch statement here since we already know how to write these. We are simply mapping each of the values the user can enter to a switch section that will run the corresponding report. I've also included a default case just to let the user know that what they entered isn't a valid option, in case they try to run a report that does not exist in our list. Let's set a breakpoint at the start of our switch statement and then launch the app with our debugger. We should see the console boot up, and it displays our message asking for which report to run. The available options are also shown in parentheses. So let's enter in tasks as our selectedReport, and then hit Enter to run it. This will cause us to jump over to our switch statement breakpoint. When we hover over our selectedReport variable, Visual Studio will confirm that our console input was captured correctly and stored for us. And if we step through the switch statement, we'll land inside of our TaskReport case which matches the selectedReport.
Let's click Continue to finish out the Main method execution. When we look at our console again, we can now see a list of tasks printed out for us. This is the first time that we, as a user, were able to dynamically or interactively change the flow of our program.
Many sophisticated applications are built on this idea of orchestrating code execution based on user interaction. It's great that we were able to run this report, but the end result is a little bit awkward, as the app just stops. What if we wanted to be able to run multiple different reports one after the other in the same user session? Well, let's see how to make that happen next.
Chapter 13 How to Enhance Program Flow Using Loops
Let's enhance our program flow to allow the user to run multiple reports without having to restart the app. So at this point, you might already have an idea of how we can improve our user experience. If not, just ask yourself what type of statement would allow the user to repeatedly run different reports based on some criteria. Well, if you guessed a loop, you are correct. More specifically, we could actually use a do while loop to accomplish this. We always want the user to be able to select a report at least once and then have them run additional reports as long as they don't quit the app. You could also achieve this using a more standard while loop, but let's try a do while since we haven't used that option yet. First, let's enable the user to be able to keep running reports until they explicitly say they want to quit out of the app. At the top of our Main method, let's create a variable to track whether the user has chosen to quit.
So let's type bool quitApp = false. As long as this variable is false, we want our app to keep prompting the user to run another report. Next, let's wrap our existing code in a loop to make it repeat. So above our console interactions, let's type the do keyword and create a pair of curly braces. With a do while loop, whatever we put inside of these braces, it's guaranteed to run once. So let's actually move all of our previous code inside of this do code block through a simple cut and paste. We now have the do part of our statement in place, but we also need that while check at the bottom. The while condition is evaluated each time after the code block runs. So let's type while, and then what do we want to put inside of these parentheses? Well, that's where our quitApp variable comes into play. We can simply type !quitApp. The exclamation point is called a logical negation operator, and it flips the current value of a boolean.
So as long as the user has not said they want to quit, the quitApp variable will continue to be false. However, our negation operator will cause it to register as true for our loop and will keep prompting them to run a report. The last thing we need to do is add a case section to our switch statement that allows the user to quit. So above our default case, let's add another case label, and this will check for a value of quit. Below that, we can simply set our quitApp variable to true and then add our break. When the user types quit, the boolean will switch to true, and that means our while check will evaluate to false since the negation operator will flip the value, and our app will exit. Let's also remember to add quit as an option to our initial prompt just so the user knows they can actually type that. As a side note, for this example, I intentionally wanted to highlight the use of the negation operator since we haven't used it yet. This operator can also be used in other selection or iteration statement expressions and checks. However, you could, of course, set up this example using the inverse where the quitApp variable is instead called something like keep running, and it defaults to true. That inner switch statement you could explicitly set it to false to make the app stop, and a negation operator would not be required. This type of decision comes down to readability and personal preference. The last thing we could do as a simple UI enhancement is to add an empty WriteLine statement at the bottom of our do block. That way, when the prompt repeats, there will be some decent spacing between our runs. Once that's in place, let's start up the app and see what happens here. Once the console loads, everything should look the same.
And this time, let's again start by typing tasks. We should see our familiar report printed out, except this time, we are now prompted to run another report instead of the app just awkwardly ending. So let's type rewards and hit Enter, and now a different report will be displayed in the same session. Of course, we can also type quit, and the app will stop, and then we can hit any key to close out of the .NET console itself. Our app's program flow is now controlled almost entirely by the user. However, one aspect of the setup is still very static. We're always running reports against the same set of data. Next, let's explore options for introducing more dynamic datasets into our program flow.
Chapter 14 Program Flow Considerations with Dynamic Data
Let's explore how to update our app and program flow to work with more dynamic sets of data. Throughout this book, we have utilized a static or dataset that we've called Q1Results. This represents a set of exported data that Wired Brain would receive from their simple survey tool for the first quarter of the year. However, as the year goes on, they would like to be able to analyze the results from the second, or third and fourth quarters as well. Right now, our app cannot easily adapt to that scenario, so let's see how to improve this situation. In a real app designed to process survey results, we, of course, would not just receive a C# class with values in it. Using globally available static values like this is actually a bad practice in many situations. Instead, we would retrieve that data from a web service or database, or perhaps through exported files in simpler cases.
That data would also be scoped more carefully to the required methods that use it and managed by our program flow. Let's pursue that option of using file exports to get our data. Each of these files represents the exported survey results for a different quarter of the year, and of course, more would be added as time goes on, so Q3 and Q4, and then future years. If you're not familiar with JSON or JavaScript Object Notation, as it's formally called, it's a very common type of data format. Despite the name reference to JavaScript, it's a fairly way to define and transfer data between different apps. So let's open up our Q1Results.json, and right away, we can see that structurally, it is very close to our original Q1Results C# file. JSON uses objects, arrays, and key value pairs to hold sets of data.
In fact, I can open these up side by side just to compare. So, I'll rearrange these real quick, and of course, we can see that they are very similar.
We have our aggregate scores at the top, such as our coffee score or service score and so on, as well as our individual responses with our comments down below. In JSON, those comments are represented by an array, just like we used a list in our C# code, and if we were to open up the second quarter JSON file, we can, of course, see the same structure, but with a more recent or updated set of values. Well, we want our updated app to be able to read in these JSON files dynamically. Because our JSON keys have the same names and structure as the C# properties of this class, we could map them over to our class to use throughout the app. We could also enable the user to select which file to pull data from to run our reports, just like we previously allowed them to select which report to run. So to start with, let's prep this Q1Results class to be more reusable and be able to handle mappings from our JSON file. First, let's just step through here and delete all of the value assignments. We no longer need these values, since they will be pulled from our new datasets and mapped to these properties. So just take a moment to go through these, and at the bottom, also make sure to
delete the list of individual responses. You can replace that list with get and set accessors like the other properties up here, so we'll be able to map the array of responses from the JSON. Let's also get rid of the static keyword everywhere in this file. In simplified terms, static makes these properties globally available across our app by associating them with the Q1Results class directly, rather than an object instance of that class. If you are not entirely sure what that means yet, no worries, just make sure that you remove them for now. The last thing we need to do is rename this class. So I'll just switch this to something more reusable or generic like SurveyResults. Make sure to also rename the survey results file in the Solution Explorer, which is an easy step to forget.
When you're finished, if we were to try and build the app, we'll get a lot of errors in our consult, since we've broken the references to our Q1Results class. Next, let's see how to complete this setup by dynamically selecting and loading in our JSON data. Let's finish enhancing our app's program flow to be able to dynamically process
different datasets. Back in the main method of our program, let's leverage our JSON data and the new SurveyResults class to improve our setup. Currently, we collect input from the user to select a report to run, but we do not ask them what time frame they would like to pull that data from.
So below our first input, let's add another Console.WriteLine statement that says please specify which quarter of data, and again, provide some examples in parentheses like (q1, q2) or so on. Below that, let's add another variable called selectedData and set that to equal another Console.Read line for whatever they enter. Next, we need to read in our JSON file and map it to our SurveyResults class. For this, I'm just going to paste in a line of code that you can use for reference since it does require a few concepts that are beyond the scope of our discussion. This code uses the .NET file helper class that we looked at earlier to read in our raw JSON data files. We also pass in the selectedData variable as part of that file path to grab the data file the user selected. We then use the JsonConvert class, which is also provided by .NET to map the broad data to an instance or usable object of our SurveyResults class. That object is stored in a surveyResults variable, which we can use to access our survey data just like we did previously with our Q1Results class. Now, because the survey results are now stored in a variable that is
defined in our main method, that variable is automatically scoped to just this method. Remember, by default, variables are only available in the code block they're created in and any nested blocks. Our report methods down below are entirely different methods, not just nested code blocks. So for them to be able to use this variable, we have to pass it as a parameter. So, let's copy and paste the survey results into each of these parentheses like so. At first, Visual Studio will complain to us that this is not acceptable. Well, that's because the signatures for these methods do not currently accept a parameter. So further down in our report methods, we need to add that new parameter to the method definition. On this GenerateWinner report, let's add the SurveyResults as a parameter and name that parameter results or something similar.
Once we have that in place, we can then copy it and add it as a parameter to our other two methods. This will get rid of the original errors up top, but we do still have some errors throughout our app with all these red lines under our old Q1Results references.
We have to change all of those references to instead use the results variable that is passed into the method. Remember, we are no longer using this static data. We want to perform our logic on the survey results that are dynamically provided as a parameter. An easy way to accomplish this is just through find and replace. So, let's hit Ctrl+F to bring up the search box and then click the arrow on the left to expand this to include the replace feature. In the first input, let's type Q1Results., and that will find all the references to our old class, and then in the replace, just type results., which will update all those references to use our new variable when we click replace. Our program file here is now set up much better. We no longer use hard coded, data. Instead, we dynamically read data in from a file, store it in a variable, and pass it down into methods that will use it to perform logic. This setup is closer to what you would want to use in a real app. If you do not understand every aspect or step of this process, that's totally fine for this book.
The key concept here is that we're now able to use user input to control our program flow and select which file to read in and use
for our reports. Let's start our app and see what this gives us. Our console should load like usual, and it will ask us which report to run. So let's enter tasks or whichever report you'd like and press Enter.
Next, it will ask us which dataset we'd like to use, which is a new feature. Let's pick Q1 and hit Enter again, and a familiar set of tasks will be printed out for us. However, these values are now dynamically pulled in from our JSON file rather than a static class. Our app will prompt us to run another report, so let's choose tasks again to use for comparison. This time though, let's choose Q2 as the data source and hit Enter. This time, we'll see a different set of tasks printed out compared to our first result. That's because our Q2.json file has a totally different set of data and values, so our reports generate different outcomes. Now we can add additional JSON files
to our app as more results come in, and the user would be able to keep running different reports. This is a much more flexible and useful solution than what we had before. Next, let's start to talk about some additional more advanced features of selection statements that we can apply to our app.
Chapter 15 How to Explore Advanced Selection Statement Features
As you start to work with selection statements in more depth, you'll find there are some alternative syntaxes or additional features for these statements and the expressions that they utilize. Let's say we have a simple if/else statement that conditionally assigns a value to a discount code variable.
If the category is Coffee, the discount rate is .25, or 25%, and for other categories, it's 33%. While this code is technically fine, but it also creates about 10 lines of code for a simple conditional
assignment. These scenarios can be streamlined using a C# feature called ternary statements. Ternaries allow us to place a Boolean expression on the right side of a variable assignment, followed by a conditional operator.
The conditional operator is a pair of question mark and colon symbols, and it's also referred to as a ternary operator. The value that's placed right after the question mark is assigned to the variable if the Boolean expression is true, while the value after the colon is assigned if it's false. You can also substitute calls to methods for the static values seen here.
This essentially allows us to write simple if/else statements in a single line of code. And this brings us to an important point about ternary statements. These statements are primarily intended for assigning or selecting values, not for traditional program flow. Although they can conditionally select methods to run, those methods must also return a value to be assigned. Ternaries cannot select more complex or branching blocks of code to run, like a traditional if/else statement could. Switch statements also provide shortcuts when assigning values to a variable. So in this case, we are using a switch statement to check the friendly name of a product and then assigning a category id for it to a variable.
Well, this index could be greatly simplified using the switch expression syntax. This option defines an expression to match a set of patterns against. Those patterns use the C# lambda syntax to return a value if a match is found, such as the if productName here matches Granola. This code is much simpler and more compact, and that makes it easy to use in different parts of our app.
These expressions do have some limitations though, and are mostly used for variable assignments like this. Switch statements can also be paired with the useful when keyword. This keyword essentially allows us to add an additional Boolean expression to our case labels for more complex match selection.
We can declare a local variable for that case statement that makes a copy of the switch expression value. So in this case, the discount id would be copied to id, and then we use that as part of another expression. If that nested expression evaluates to true, the corresponding switch section will run. The last feature of switch statements I want to mention is their support for tight matching. Now this topic is a bit more advanced and requires a decent understanding of the C# type system, so we are going to spend minimal time on it in this book.
For now, just be aware that we can test whether a switch expression can be converted to a certain type. So for example, here we are verifying that a shape is of a certain type and that its dimensions are of a certain size. Just keep this feature in mind as you start to explore more advanced C# features later on. Let's start to explore some of these concepts. At the bottom of our tasks report, we created this switch statement that creates tasks based on the area that needs the most improvement.
Well, if we think about this statement, basically all of the cases are doing the same thing. They're just creating or assigning a new task to our list. We could streamline this scenario with the use of switch expression syntax. So for the sake of comparison, let's recreate this same statement a couple lines up. So let's create a new task called improveTask.
And then on the assignment side, let's add the value we want to compare against, which is results.AreaToImprove just like below. And then we'll follow that with our switch keyword and an empty block of code. Remember, with this style of syntax, the switch keyword comes after our item to compare against. Next, we can simply create checks
against that area to improve and use a lambda operator to return a corresponding value. So for example, we could say RewardsProgram as the first option, and then the lambda operator, and then say Revisit the rewards deals. So if RewardsProgram matches the AreaToImprove value, then Revisit the rewards deals will be assigned to our task variable. We can continue to add other scenarios as well, so I'll run through the cleanliness scenario real quick and then the mobile app one down below just using our other statement for reference.
Whichever one of these values matches, the corresponding task will be assigned. I also want to highlight that we can use the C# _discard syntax to act as a default case if none of those others
match. And then we can just return our generic task of Investigate individual comments or something like that. And make sure to round out this overall statement with a semicolon as well. Now that we have our task assigned to a variable, on the next line, we could then, of course, say tasks.Add and pass in our new improveTask just like usual. This switch statement is now about half the length of the original. However, we could actually take this one step further. Remember, our switch here is written as an expression. That means we could actually just pass the entire thing into our tasks.Add method, and we would not need this extra variable. So let's cut our assignment out of here and then paste it down below right into our parentheses. This is valid syntax, and it further simplifies our code. We can also just delete the old switch statement out of here, and now we've successfully streamlined our program flow. There is also another scenario in here where we could leverage another type of switch statement. Further up, we have this branching if else conditional, which checks the response rate and then performs different tasks. This approach is fine, but if we had more than three scenarios, this approach could quickly become very verbose. For example, let's say we wanted to take different actions for every scenario between .1, .2, .3, .4, and so on. Well, an easier way to do this would be to use a switch statement with additional when logic. So, for this example, I'm just going to paste in a completed example since the code is very similar to what we just worked on with one new feature. With this switch statement, we use the response rate as a starting point for another set of nested expressions or comparisons. We accomplish this using the when keyword, which lets us copy the response rate down to a local variable to use in another expression. If any of these nested expressions return true, well then the corresponding value is returned from our switch expression.
So in this case, we have reduced our if else statement from 12 lines to about 6. However, in this case, I personally think the original statement is actually easier to read and understand, so I would prefer to keep that one until more complex logic or many more cases are needed. These are the types of personal decisions you'll have to make when trying to set up the program flow of your apps to be as readable as possible. For now, I'm just going to delete this new switch statement out of here, but I encourage you to explore these different syntax options more on your own. Now let's look at how we can use ternary operators to simplify our code and discuss a couple related features and best practice considerations along the way. Ternary operators provide a very compact way of adding additional assignments or selections to your code. These ternaries work best when they're applied to a standard if/else type of scenario. Inside our tasks report again, our second selection statement is currently set up that way.
Let's rewrite this block of code as a ternary to see them side by side. So above that statement, let's define a variable to hold the result of our ternary operation. So, I'll type out var newTask =, and then let's say overallScore > 8, which will still act as our conditional, just like below. And then we could use our ternary operator for the true scenario, which will result in a value of Work with leadership to reward staff.
Then we can add another half of our operator with a colon for the else condition and provide a value of Work with employees for improvement ideas. Now after this expression is evaluated, it will assign a task to our variable. On the next line, we can then say tasks.Add(newTask), and add that to our list. This code now produces the same result as the eight lines below it using only two lines. However, we can actually simplify this further by just passing the entire ternary expression into the tasks.Add method, similar to
what we did with our switch expression previously. So if I were to cut the right side of this statement out, we could then paste it in place of our new task and then just delete this extra variable. We have now reduced our if/else statement all the way down to just one line of code instead of eight. Although this code is technically correct, you should be cautious when using ternaries. Many developers actually don't like using ternaries because it's easy to miss small details or create quirky behaviors. If you start to add this type of logic everywhere in your code, things can start to get a little bit messy. On the other hand though, in scenarios where you just want a quick and easy conditional without the use of multiple code blocks, ternaries can be a great solution. There's one other shorthand feature of if/else statements that you should really be aware of, but with some caution.
In C#, curly braces are technically optional on if/else statements. For instance, we could remove them from our top if statement, like so, and this code would still compile and actually work just fine. However, I would strongly advise against using this approach in your code. Most developers will agree that this is a bad practice in pretty much all scenarios in C#. Without start and end braces, it's very easy to accidentally move lines of code around and forget to line things up or group the wrong items under this if statement, and it just creates bugs. I am only covering this approach so that you understand the syntax in case you see it in other existing applications. Next, let's start to wrap up by reviewing a few essential
ideas. Let's review a couple changes in concepts around the final state of our application. These changes improve on ideas that we've already explored and provide some new topics for you to consider well beyond this book. Back in Visual Studio, you might have noticed that our Program.cs file has become very large over time. Keeping files and classes to a reasonable size is one of the most common challenges in programming. Code should be split out into logical, reusable, and testable pieces throughout our app. That means no single method or file should grow to an unmanageable size. In this example, you'll immediately notice that our Program.cs class is much smaller. That's because in the center here I have extracted our data retrieval and our report logic out into their own service classes. Services are a common term for any class that provides business logic or performs a service for your app, so rather than our main method retrieving the JSON data directly, it now relies on a data service with its own methods to handle all that for us. This provides all kinds of benefits, including reusability, abstraction away from the details of how this data is actually retrieved, and much more. That same concept applies to our report services.
So if we were to navigate down into our TasksReportService, which we can do by and choose Go To Implementation, we'll find the same GenerateTasksReport method that we built earlier. However, this method now belongs to the Service class rather than the main Program class. We could now use this method anywhere throughout our app as long as it's provided survey data as a parameter.
At this point, we are also still mostly using static classes, which means our methods are widely available anywhere. However, as you get deeper into programming and the C# type system, you'll also learn how to use instances of classes rather than always having to rely on the static class itself directly. I encourage you to pursue learning more about these types of concepts for building apps with superior architectures. Let's revisit our main method just one final time. If you do not understand all of this class verbiage and concepts around types, that's totally fine. The key takeaway here is just that we can extract our logic and methods out into separate classes or files for re use across our app. Our program flow and selection statements can control when those classes are utilized and what types of operations they can perform. Just keep these types of concepts in mind as you advance further into your C# journey, and you'll be well on your way to designing more advanced applications. As we wrap up, let's review a few key concepts about program flow and statements. Program flow can be controlled at both the macro and micro level in our app. Conditional and iteration statements can be used to execute a few statements inside of a single code block or
control entire methods and manage larger sections of our app. Program flow can also be influenced by user interactions or behavior. We can capture their actions or choices and use those selections to make decisions in our app. C# also provides some useful alternatives to selection statements for handling specific scenarios or reducing the amount of code that we need to write. For example, we can use the ternary operator to streamline a conditional value assignment or selection. We can also use the switch statement expression syntax to simplify variable assignment. These expressions can be used inline anywhere that expressions are supported, and they greatly reduce the amount of code needed to assign values. C# also allows us to use pattern matching syntax with keywords like is and when. Pattern matching can apply additional checks to our if/else and switch statements. This can improve accuracy and handle additional scenarios with more complex requirements. So, we finally reached the end of our exploration of program flow in C#. If you only take away one concept from this book, just remember that we can use different types of statements and expressions to control the execution of our apps. We can make decisions with selection statements, execute tasks at scale with loops, and compute results using expressions. These features help our apps evolve beyond a simple set of instructions to programs that achieve meaningful goals through complex decisions. I encourage you to continue this learning path and explore how these concepts apply to other C# features, like the type system or LINQ in programming.
BOOK 4
C#
TYPE CLASS FUNDAMENTALS
BUILT-IN DATA TYPES, CLASSES, INTERFACES, AND INHERITANCE
RICHIE MILLER
Introduction
This book has one goal; explaining and teaching you about probably the most essential topic in C#, and that would be types. Types, what are they? Well, no worries as we'll learn about them. Basically, everything you'll do in C# has to do with types, from the simplest variable declaration to creating a very complex class. All of this happens through the use of types. Did I say they were important? Well, yes, you can't live without them really, or at least you can't write any C# code without them. Once you have finished this book, you will understand classes - the most fundamental building block in C#, and it will definitely get you on your way to writing more complex C# applications. I do hope you'll follow along with everything I'm doing in this book as I am convinced that that is the best way to learn. Everything in this book is made so that you can easily follow along. Now that I have hopefully excited you with all the good stuff that you'll be learning in this book, you may be thinking, am I up for the task? Well, although you'll be learning quite a lot, what you need to know is actually pretty limited. Just the C# basics explained in the other books in the C# learning path before this one will do fine. That translates to, not that much really. If you understand general C# syntax, knowing that a line ends in a semicolon, if you know what an if and a while statement does, and you understand that using Console.WriteLine is going to send text to the console, you'll be good to go. In this book, you'll be focusing on the C#
language and, thus, will keep using the console the entire time. While we aren't really building a full application, all examples that we'll create are related to the same business case. In this book, the code that we will write will be related to the HR, so the human resources software we're going to create for a Shop. You will therefore see me refer often to concepts such as employee, wages, and so on. But what do you need to follow along with this book? Well, good news on that front too. It's pretty simple. You just need Visual Studio 2019, which is what I'll be using throughout the entire book. For what I'm doing here, any edition will do, including the Free Community edition. Visual Studio exists on Windows, and there's a version for Mac as well. If you can't use Visual Studio, Visual Studio Code, a lightweight editor, will also do to follow along. With this out of the way, we're now fully prepared to start learning about types in C#, and that's exactly what we'll be starting with in the first chapter where I'll show you everything about the data types.
Chapter 1 How to Use Built-in C# Data Types
C# comes with a number of known data types that you'll be using in just about every application you'll build with the language. In this chapter, we'll start learning about data types by looking at the most basic ones, that is the types that come with the language. This will get us to write code, and it actually contains quite a number of interesting topics. We've been talking about types like they are the most trivial concept, but in fact, I haven't explained what types are in C#, so let's start with that. Then, as said, I'll show you the data types and probably you will recognize a few, maybe from other languages you have already worked with. A specific mention goes out to the DateTime type, so we'll look at that here too. Once we know more about data types, we'll start to explore how we can convert between different data types, an operation you'll do more often than you may be thinking at this point. And we'll finish off by exploring the concept of implicit typing using var. Time to make sure we all get to understand types in C#. Once we know exactly what they are, we can start using them. Let's start with an important statement: C# is a strongly typed language. This means that all variables we'll use will have a type specified when using them. And, there is a safeguard in place as well. When we write code, the compiler will
ensure that the types we are using for the variables throughout their lifetime stays the same. The type is used to specify what data will be stored in the variable, be that an integer value, a string or a custom type. We'll write expressions as we'll soon learn. These expressions, code really, will often return a value, and that too will be of a given type. Here the compiler will also check that the types match. By specifying the data type for a variable, we are specifying the size that C# or, in general, .NET will reserve in memory for of a variable. Also, the data type we're using will be a deciding factor on where the data will be stored. We'll learn soon that depending on the data type, some data is stored on the heap and some is stored in a stack. The data type will also give us information about the range of data that can be stored. Data types will typically have a minimum and a maximum value. Also based on the data type, we'll be able to perform certain operations on it. I can, for example, add together two integers, but I won't be able to just out of the box add two objects together. No worries if this sounds a bit confusing, it will all become clear soon. Finally, the data type or types we use in an expression will also play an important role on the type of the return value. Again, we will see this in action very soon. Basically, we can say that there are two groups of data types in C#. We have types that come with C# that are called predefined types. They are built into the language and can be used out of the box. We'll start working with these in this chapter. Next we have the ability to create custom types. Creating and using custom types is a very common thing, and this we'll start doing later in the book. As said, let's for now work with the data types. C# comes with a number of types built in. The bool data type is used to store Boolean values, so true or false. Int is used for integer numbers,
so whole numbers, and in C#, int is basically an Int32, which uses 4 bytes to store data. The float, the double, and the decimal types are used to store floating point numbers where the decimal should be our choice when we need the highest precision. Then there's the char data type, which is used to store Unicode characters, such as the letter A. We're not done yet. The byte type is used to store small, integers, basically limiting it to a value between 0 and 255. Indeed, only positive values, otherwise you need to look at the sbyte. The short type is used for integers and cannot store somewhat larger values. Next, there's the object type, predefined also. We'll use this object type a lot when we start building our own custom types. Finally, another type is the string type. It's a bit of a special case. It's even worth an entire chapter, so we'll explore the string in the next chapter. Depending on the data type for our a variable, C# will treat it differently. The value will be stored in a different memory location. We'll explore this in much more detail later in the book, but I want to touch on this already here just to make sure you've already heard about it at this point. Basically, data types can either be value data types or reference data types. Value data types are stored on the stack, and we'll learn about that later on. But in this case, the variable that we'll use will directly contain its value, hence the name. Under value data types, we find some of the predefined ones such as the integer and Boolean types. Also, custom, so types can be value data types. We'll learn later about these, and this includes the enumeration and the struct. No worries is if you don't know what these are. We'll spend a lot of time on them. Reference types work on references, which explains their name. In the case of reference types, the actual data is stored on the heap. Under reference types, too, we can find both predefined ones, and
this includes things like the string and the array and custom classes, as well as interfaces. Covered later, too, are reference types. All types in C# live in a hierarchy. We'll dive in this more in that later on, but here you can already get a peek of how the types relate. All types we just looked at, so float, Int32, and the like, are structured underneath a parent data type called ValueType. Other reference types are shown here in the graph on the left. In the end, all types are children of a base type called object. One more thing before we start using types. In C#, there are known keywords for the value types. The reason for this is simple. We use them so frequently that a shorthand is available to create a new one. So C# knows about int, all in lowercase, but behind the scenes when we will create an int value, in fact an instance of the Int32 type is created, and the same goes for float, decimal, and pretty much all the primitive types we just looked at. Now that you have a understanding of what data types are and how they relate to making C# a language, let's start using the data types. When we decide we want to store an integer value, let's say, we can create an int variable and give it a name.
Here I'm doing exactly that by specifying int a = 2. A new variable of integer data type is created containing the value 2 and name a. We can also write an expression on the side, as we're doing here in the second line of code. Here I'm specifying that the result of a + 3, again an integer value, should be stored in a new variable of type int, and that's called b. This line of code also shows us that the expression returns a certain value.
It basically decides what will be returned, and this needs to be captured on the side. Say you want to create a Boolean value called c. Well, we then can create a bool c and set it to either true or false. Here I'm setting it to true. In C#, we need to include the data type upon declaration. And once defined, the variable can't change its type later on, so moving from runtime to the other simply is not possible. Since C# is a language, it will guard at all times that types are respected, and this is verified at compile time even, so when you compile the application. Take a look at the snippet here. We are first creating an integer value called c and I set it to 3. In the next line, I'm then trying to set it to true, which the compiler simply won't allow. Now why is that? Because true, the side here, is a Boolean value and that cannot be stored in C because that was declared as an int and it will stay an int forever. Having these checks at compile time will save you from a lot of runtime issues, which occur in other languages that don't apply type safety.
Chapter 2 How to Work with Primitive Types
Let us in our first demonstration go to Visual Studio and work with primitive data types. In this first demonstration, I'm going to show you how we can create variables using the data types, so the primitive types that come with C#.
So, we'll create a new project here in Visual Studio, and we are going to select a console application. Make sure to select the .NET Core Console Application since we are using .NET Core throughout this book. Whatever you're seeing here in this book will also work fine with older versions of .NET, so the regular .NET Framework, that C# code that you see in this book, will work fine there as well. But we are going to do things on the .NET Core Framework. Select Next and then enter the name of your application. Click on Next, and now Visual Studio will ask you what version of .NET you want to use. At the time, .NET 5 is the latest, but older versions will work fine for what we are doing here in this book.
So if you want to run this on an older version of .NET, that's also going to be fine. Click on Create, and Visual Studio will go ahead and create our new application. This is a console application which has a class Program and a static void Main. I'm going to replace this Console.WriteLine with a Console.ReadLine to get started with, and then the console window will wait at the end when we run our application.
I'm going to first create a few variables, and creating a variable requires that I first enter the type. Assume that we're building an application for our employees in Shop, we probably want to pay them a wage. So, I am going to capture the wage in a variable. I'm going to start in C# by typing the type, it's going to be an int in this case, and I'm going to call the variable monthlyWage. As you can see here, I'm using camelCasing, that's typical for local variables that we use in C#. So I'm starting with a lowercase, that is typical for C#. I can actually add a semicolon here, which basically creates my variable and doesn't give it an initial value. I can also add an operator and equal sign here and assign an initial value immediately. This is an integer value, so I have to give it a whole number, for example 1234, and I end this with a semicolon again. You may be thinking, am I doing something wrong? Visual Studio is giving me this green squiggly here. It is saying that because you have created this variable, but it's never being used at this point yet. So it's just a little warning that it is giving you. I'm going to create another integer variable called months, and then I'm going to set it to 12, and in one go I can also, for example, say also create another variable called bonus and set that to 1000. We can indeed in one line add multiple variables. C# comes with a number of types, not only integers. Say that you wanted to capture a Boolean value. Well, then we can type bool, and that could be whether or not an employee is working at this point, so we can create a variable, isActive, and set that to true or false. If you want to create a double value, well, C# comes with the double keyword built in. That's, again, a keyword that stands for the double type. And let's say that we want to give them a rating as well that might not be
a whole number. So let's say that we want to give a rating of 99.25. Well that's a very good rating, that must be a very good employee. Not only is C# going to check that the value that I assign here on the red matches the type that I have defined here on the left, it's also going to check that the value is within a certain range. Now this is easy to check, for example, with a variable of type byte. Now bytes can only be between 0 and 255. Let's say that Shop is growing, and initially we have created a variable, numberOfEmployees, and like I said, bytes can only contain a value between 0 and 255. If I assign, for example, 300, so Shop is really growing, you immediately see that Visual Studio give me a red squiggly now saying that this value actually falls outside of the range. So this value of 300 cannot be converted into a byte. So this I have to comment out because otherwise my application won't compile anymore. I said C# is a type safe language. Once I have decided that monthlyWage is going to be an integer, I cannot in the next line, or somewhere else in the application, all of a sudden decide it needs to contain a Boolean value. So I cannot do the following. I cannot say monthlyWage = true. Again, Visual Studio will give me a red squiggly saying that what I'm doing here is not a value that can be assigned to monthlyWage because, indeed, monthlyWage is of type int and true is, , a Boolean value, and it says here I cannot convert this into an integer value. Type safety will check at compile time that my variables get the correct type, and it will do that all the time.
Chapter 3 C# Expressions
Now just declaring values won't get us very far. We need to perform actions with our data. Expressions are basically what you can find on the side of the equal sign. They can be very simple, just assigning a value, but it can be much more complex too.
Very often in these expressions we'll use operators. C# comes with a large set of operators, and you already can see a few in action on this screenshot here. As said, there are quite a few operators available. It's not my goal to explain all of them in this book, but we'll look at a selection and you'll see them being used, well, throughout the entire book.
First we have arithmetic operators used, indeed, to perform calculations. Equality operators are used to compare values and logical operators will perform logical operations with Boolean values. Assignment operators are used to assign values, as you may have guessed from the name.
Here you can see a few arithmetic operators, and I'm sure you remember most of them from math class. The double plus is worth mentioning here. It is used to increment the value of the operand with 1.
It's often used in combination with a loop to add one to the counter, counting the number of iterations. When we want to ask C# to check if two values are equal, we can use the double equal sign. This will return a Boolean value, true, , in the case the values of here, a and b, are equal. If we want to check if a and b are not equal, we can use the inequality operator, the exclamation mark, and then an equal sign. This will return true if a and b are not equal. Operators also exist for checking whether their value is less than or greater than another value. And, if you bring in an equal sign here, this will perform a check to see if the value is less than or equal. We've already seen a single equal sign, which is the default assignment operator, but there's another interesting one on this screenshot here, the +=. This operator will basically say add 3 here to the current value of a. So it's the same as saying a = a + 3, but it's an often used shorthand. Now let's return to Visual Studio and start using the operators, and while we are in Visual Studio, I will also show you that based on the given data type a default value is assigned. So let us now work with operators. To do that, I'm going to create a few extra variables. I want to calculate what is the monthly wage of an employee, so I'm going to bring in a ratePerHour, which is a double, and another variable, numberOfHoursWorked, which is 165, which I think is about typical for a regular month. What is this employee going to get in terms of which?
I'm going to create another variable, which I'm going to call currentMonth Wage, and I'm going to assign that to the product of the the ratePerHour. And you can see that IntelliSense automatically suggests that I use ratePerHour, and I do that times the numberOfHoursWorked. Now I have a simple arithmetic operation on the right, and the result of that is assigned to this variable. So we don't always have to specify the value immediately, it can also be the result of a calculation, an arithmetic operation on the right of the assignment operator. And that's, again, also an operator that we're using here. We had already used it without knowing. Now I can see the result of that by saying Console.WriteLine and I'm going to output the currentMonthWage. If we run this, we see the result, which I assume is going to be correct. And depending on your location and your settings, this might be formatted differently. Now C# is pretty smart. If I, for example, add a + bonus here, it will know that it has to first create the product of these two and then
add the bonus afterwards. If we run this again, we should see 1000 more. That seems to be correct. There are other assignment operators. One that I particularly like is the following. Say that I want to add to ratePerHour, let's say, 3. We want to increase the rate per hour. I can do that as follows by saying +=, and then I say 3. This is the same as saying that ratePerHour = the result of ratePerHour, the current value that is, plus 3. This assignment operator is a shortcut I use frequently. It's a simple way to increase the current value of this ratePerHour variable with 3.
We can also do equality operators in C#. I can, for example, use the currentMonthWage value and do a check with an if statement to see if the value is larger than 2000. And based on this, I'm going to again to write something to the console. Another operator that is frequently used is the double plus or double minus.
Say that we have the numberOfEmployees, and this time I'm making it an integer value because I assume that we can grow quite a lot, and let's say that we have 15 employees currently working for us. If you want to increase or decrease that value, well, we can use the double plus or double minus. Say that someone leaves the company, and we're all sad about that, we can now say that numberOfEmployees with the double minus sign behind that. That will decrease the current value of numberOfEmployees. If I put the breakpoint here, and then you'll also see how I can do that, I can in the debugger take a look at the value of numberOfEmployees. Let's do that. And now Visual Studio will hit that breakpoint, and notice that numberOfEmployees is now just 14. The value was decreased with 1.
I want to show you one more thing in this demonstration. If I create a Boolean, let's now for simplicity call it a, and I create an integer, and let's call that b, and I run it again, I want to show you what default values C# is going to use.
Automatically, each type has its default value, and for a Boolean that will be false and for an integer that will be 0. Each of these primitive types comes with the default value built in and it's assigned if we do not assign a value. We've now seen how we can perform very simple operations, but there's so much more we can do with data types.
They are actually very clever. When we, for example, want to work with the maximum value an integer can contain, we can use a member. Now, a member, you may say, what is that? Well, that's a great question. Remember that I said that while we use int as a known keyword, there's a type built into C#, In32. That's a type that has a lot of functionalities built in. Members is the group name to point to data and behavior on a type. By putting a dot behind the type name here, so here the int, we can access its members. And when we do that on the int, we'll get, for example, access to the int.MaxValue and int.MinValue. This will assign to a variable the maximum or minimum value that the integer type can contain. Remember that the data type defines also what range is possible for a value, and that's what you see happening here. And all the types have similar members, like for example here the double on the last line.
We haven't worked with it a lot yet, but the char is a very interesting type, used, as said, before to store single Unicode characters. On the char type, there are quite a few members defined. Say we want to accept input in our application and we want to check what the user has typed. We can check if the given value is a whitespace using char.IsWhiteSpace, passing in the character variable. Note that to the IsWhiteSpace member, which is a method here by the way, I'm passing the char variable, myChar, which in turn
contains here the value a. Similarly we can check if the value is a digit or a punctuation character. Other members exist. Take your time in Visual Studio to play around with the available members. I will now also jump back to Visual Studio and show you some of the members for the int and char types.
We're still here in our static void Main, and now I'm going to see what members that the integer and let's say also the char type do contain themselves. Say that I want to check what is the maximum value that can be contained in the integer type. I'm going to create a variable, and I'm going to call it intMaxValue and I'm going to set that now to int, and now instead of creating some variable, I'm
going to add a dot, and then Visual Studio's IntelliSense suggests to me the options, the members, that I have on the integer type. This will all become clear later in this book, but these are things that the integer type knows about. For example, I can ask it, hey, what is your max value? And similarly, I can also do the same thing for min value. This will be large numbers. Let's run the application and take a look in the debugger how large and how small they are. So this is the max value that an integer variable can contain, and this is the minus version of that. This integer keyword actually represents the Int32 type, which is a struct, and we'll learn later about structs. But this is actually a shortcut to avoid us from always having to type System.Int32. But I can still do that. I can in fact write Int32, and that would have the same members as the int keyword because it's exactly the same that they're pointing to. And that's for the first. I'm also working with another type, which is the char. The char is typically used for a character. It can contain just one Unicode character. Say that I want to allow the user to enter a selection, and I'm going to capture that. For simplicity's sake, I'm just going to create a variable here, and I'm going to assign it a value and char will always be delimited with single quotes. Keep that in mind for the next chapter when we look at strings. We'll always use chars in combination with single quotes. Say that I want to see what is the uppercase version of my user selection.
In the same way that we've done with the int type, I can convert it to the uppercase by using the ToUpper member. In here, I need to pass my user selection variable, which basically is going to convert the lowercase, a in this case, to an uppercase a. And let me paste in a few other options. I can ask C# to check if the user has entered a digit or what the user has entered is effectively a letter. Let's run the application again and see what this will do. So the user selection is a, and notice that it also adds this 97. That is the Unicode value that corresponds with lowercase a. The upper version variable now contains, well, the upper a. It is not a digit, but it is a letter, so we can ask C# for all this type of information and more. I
challenge you to take a look at the char type at this point and try to add a few extra options here on this user selection.
Chapter 4 How to Work with DateTime
There is one more special data type I want to talk about in this chapter and that is the DateTime and its sibling, TimeSpan. Working with dates is very common in applications. We need to store when a sale has happened, we need to create a log about when a given action has happened in the system, and so on. Because they're so common, C# comes with a data type to work with them and that is the DateTime type. It's a value type as well, a one. A DateTime is used to represent a certain date, a certain time, or a combination. So it could be that we create a DateTime for March 28, 2021, 8 PM in the evening.
Another related type is the TimeSpan. This is used also for work, but it's used to represent a period in time, so for example, three hours. And to see how we can use the DateTime, a DateTime is internally what is known as a struct. Don't worry about that yet. It does, however, require that we instantiate a DateTime in a slightly different way using a constructor. Doing so requires the use of the new
keyword, which is what you see here on the first line. I'm creating a new DateTime instance and I'm passing the date value, 2021 being the year, 03 being the month, and the 28th being the day. Don't worry too much about the syntax just yet, just focus on the type for now, all the rest will become clear soon enough. The DateTime also has a lot of interesting members. Now do you remember what a member was? A type like the DateTime type has a lot of functionalities and data and they are referred to as members of the DateTime type. For example, DateTime today returns, well, the current date. And to a date we can also add, for example, two days and then the new date is returned. The DayOfWeek can be used to figure out what day of the week it is. So Monday, Tuesday, it's a useful one for me, as I often tend to forget what day of the week we're at. It's possible also to check if a specific date falls in the Daylight Savings period or not. vLet us return to Visual Studio and start working with date and time using the DateTime data type. And let's create a few DateTimes here, and we'll also look at the TimeSpent class, but first DateTimes. Now the DateTime is used, well, to represent just any point in time. I'm going to create a DateTime by granting DateTime, that's going to be the type, and I'm going to create a date here to represent the date that someone was hired at Shop. I'm going to call my variable hireDate. Now I'm going to use a different way of assigning the value. I'm going to use a constructor, and, no worries, we're going to look at constructors in much detail later on, but I do have to use this way of creating a new DateTime. I'm creating a new instance, that is, and then I need to pass in a few arguments here, values, to create that DateTime.
In Visual Studio, I can see a number of options, those are overloads. Again, we'll come to that. And these are combinations of parameters I can use to create my new DateTime. So let's create a DateTime in the year 2021, then I write a comma, then the month, let's take March, and let's take as the date the 28th. I can leave the hour and minute and the second out. I can also set them to a value or I can also set them to 0, and that would mean midnight, but let us hear select half past 2, so that would be 14 hours, 30 minutes, and hiring is not to the second, so we'll leave that to 0. So now I've created my first DateTime. I can also ask C# to write that DateTime to the console.
And let's see what that does. And there we go, we see the date, the 28th of March of 2021 at half past 2. This is Europe notation. This will depend on your regional settings how this date is being displayed here. Now as you can see, there are other options to
create the DateTime. For example, say that an employee leaves the company at this particular time, then we can create a DateTime using another overload. As you can see, it uses less arguments that I'm passing in here. Now the data is a very clever type. It will also check that the date I'm trying to create actually can exist. Now I am creating a date here in which I am using the 15th month. Some years do appear longer than others, but I've never encountered one of 15 months. Let's see what this will do when we run this. As you can see, no compile time error, but I am encountering a runtime exception. C# says that it has encountered an invalidated, unrepresentable DateTime, which is kind of logical. The 15th month still doesn't exist. So let's put this in comment because that is going to stop us from doing anything else. Now using the DateTime type, I can also do date calculations. Say that I want to represent the time that the employee will start, or the date that the employee will start I should say, is 15 days after signing the contract. So I'm going to create the start date as being the hire date plus 15 days, and I can use for that the AddDays method here. That's right, also the startDate now to the console, and take a look at the result. And now, indeed, this does add 15 days.
The DateTime type has a lot of members. For example, I can say to the DateTime type, give me the current DateTime. This will give me back the current date and time, and it can do much more. For example, I can ask the DateTime type if we are in daylight savings time, and for that I can use the currentDate.IsDaylightSavingsTime. And let's see if we are currently in daylight savings time. It seems we are not. The current DateTime is the 22nd of March, 2021.
I said the DateTime type is typically used to represent a certain point in time, but if we want to express a span, a duration of time, that is, then we can use the TimeSpan. Say that I have created the DateTime startHour and I've set it, again, to DateTime.Now. If I want to express the duration of a working day, I can do that using a TimeSpan. I'm creating here TimeSpan, and let's call it workTime, and let's set it to a new TimeSpan, we also have to use this way of initializing a time span, and let's set it to 8 hours and 35 minutes and 0 seconds. Now I can calculate when that employee will stop working. So the endHour will be the startHour, and I'm going to add to that a TimeSpan, and that is going to be my workTime. Now notice the difference here. Here I used AddDays, and that expected a double value. Here I'm using the Add method, and that expects a TimeSpan. And so let's take a look at the value of startHour and endHour.
So the startHour is the 22nd of March, 20 hour, 56, and the endHour will be somewhere around 5:30 in the morning the next day, so C# actually added this TimeSpan here. Now I'm showing you the values in the debugger, but I can also use nice string representations of TimeSpans and DateTimes. These types come with a lot of these built in. Here I'm using the ToLongDateString on the startHour and the ToShortTimeString on the endHour. These are members of the DateTime type that I can use to represent the date in a readable format. And now you see, indeed, that it is showing me a readable DateTime and a readable time of day. In this part, I want to talk a bit about how we can convert between different types in C#.
Now you may be thinking, did you not say earlier that it's impossible to change a variable's type? Why are you bringing this up then again? Well, allow me to explain. In it, what you see here on the screenshot simply won't work. It will actually result in a compile time error. First we declare a to be an integer value and we assign it the value 3, then in the next line I assign a string to our variable. Errors, errors everywhere, at least on this line. And although we can't change the type of a variable once declared, it's possible to convert its value from one type to another, and this value will then be assigned to a new variable of the target type. For this, we'll use conversions, and there is in fact a few options that we can use to convert. We can do an implicit conversion, an explicit conversion, also known as a cast, or we can use a helper. Let's take a look.
First, let me show you an implicit conversion, also known as an implicit cast. Say that we create an integer variable and give it a
value. If we now create another variable of type long and assign a to that, basically what we're doing here is taking the value of a and passing it to our new variable. We're indeed working with the value. C# won't complain about this since the data conversion, well, just works. Longs can contain larger numbers than integers can, so assure nothing will get lost. No special syntax is actually required to do an implicit cast. Secondly, let's try doing the same, going from a double to an integer. If we try to do this in the same way, the C# compilers will complain since now data could get lost. Indeed, a double can contain more than an integer can; therefore, we need to specify to C# that, well, we know what we're doing and we need to specify the type we want the value to be converted to. This is done by putting the target type between brackets before d. Now you'll see that not all conversions will basically work, and so actually they can result in a runtime exception causing replication to simply crash. Finally, as mentioned, it's possible to use a helper. C# comes with the Convert.ChangeType, a approach to allow conversions between types. Here you can see that I'm using Convert.ChangeType to change the value of d into an integer value. In the next demonstration, let me show you how we can convert between types. The C# type system will check that the value that we're assigning matches the type and also falls within the range that is allowed. But it will also check when we are converting from one type in the other if that conversion will go through automatically or that there is a possible data loss. Here we had created the numberOfHoursWorked. That is an integer value.
Say that I create a long variable. Variables of the long type can contain a much larger value than integers can, so I can actually say that I'm going to create a variable, veryLongMonth, and I'm going to assign to that the numberOfHoursWorked. So I'm now converting the numberOfHoursWorked into a long.
Since long variables can actually contain larger values than integers can, C# will allow this to go through automatically. There is no risk that data is going to be lost. But say that I do the following. I'm going to create a double, and let's call it d for now, and I'm going to assign it to this value here. Now if I now create an integer value, let's call it x for now, and I'm going to say, well, I'll set this x equal to d, I'm then going to assign the value currently in d into x in this case. But now, notice a red squiggly. Visual Studio will say that it cannot do the implicit conversion, and that's what it actually did over here. It cannot do this conversion for us because we explicitly need to agree that we may end up with some data loss, and for that I
need to do an explicit cast. By putting in the type between brackets in front of the conversion, I'm basically saying to C#, well I know that I am going to convert it to an integer, I'm okay with the possible data loss that might happen. But, implicit and explicit casts are a very common thing to do in C#. Let's do another one here. Let's say that I do an int intVeryLongMonth and I assign now, again, the long version to that. What do you think is going to happen?
Well you may have already seen that Visual Studio is again saying that it's not going to do this automatically. Possible data loss could happen, long variables can contain larger values than integers. So if I really want to do this, I need to, again, put the int type, again between brackets, in front of this long. Next to converting, there's
also a thing called parsing. Now parsing allows us to take a string value and convert it, or should I say parse it, to another type. It's a very common thing to do, but it's more related to strings, and thus we'll take a look at it in the next chapter.
Chapter 5 Implicit Typing Fundamentals
Now, I want to briefly discuss one more thing related to types, and it's implicit typing. I have already mentioned a few times that once a type is set for a variable, it's done, it's set, and so far we have been explicit about this, meaning that we included the type name, so int or bool, in its declaration. That is the normal way of doing things in C#.
Since C# 3, so already for quite a few versions, C# supports implicit typing. As the name gives away, implicit typing allows us to ask C# to figure out itself based on what is on the side of the equal sign what the type will be. We use for that the var keyword, basically saying, hey C#, here's a variable, please figure out the type yourself. Take a look here. I have here var a = 123. 123 is an integer value, and thus C# will create a to b of type integer. If we do var b = true, C# will understand that true is a Boolean value and thus b will be of type bool. You may be thinking, is C# then allowing us to omit the type?
Well, that's a great question, and the answer is it's not. It's still creating the variable with the data type it finds. The data type is decided on upon compile time, so it's known what our a, b, and d will be here before the application runs. Just like before, we also can't change the type later on. The types are still set. Using var means that the type is inferred. C# will look at the expression on the side, and based on the result of that it will know what the type of the variable will be. You can pretty much everywhere, where we have used so far our explicit typing, use implicit typing, although there are some exceptions. However, it can lower the readability of your code since it's harder to understand what type a variable will be; however, and this goes beyond the scope of this book, there are places in C# where implicit typing is pretty much the only way forward. When you learn about LINQ later in this path, you'll see that in quite a few cases the use of Hvar is required. Let us return one more time to Visual Studio and see how we can use var. So far, we have done what we typically would do. We specified type, then we specified the name of the variable, and optionally assigned a value. But, in C# we can also use implicit typing. Instead of writing the type, I can also use var here instead. The var keyword is actually usable wherever I define a variable, such as an integer value, it could be that I replace bool with it or I could also replace this double with it. What I've done here is I've used implicit typing. This can actually be used to let C# figure out the type. So instead of me explicitly specifying the type, I let C# figure it out by looking at the side. That means that if I hover over this monthlyWage, it still knows which type it is going to be because C# looked at what was on the side of the assignment operator. That's the same for this isActive and the same for this rating here as well. So by using var, I have
now all of a sudden made it possible that I change the type. I can still not say that all of a sudden monthlyWage is now a Boolean in the next line. That still doesn't work because of type safety.
You may be thinking is this really worth it? Well it is, in my opinion, often a personal preference. Some people actually like always writing var, some people, including me, actually like to be more explicit. I typically tend to always specify the type when creating a variable. It makes the code a little bit more readable, but it is personal preference. There are cases in C#, though, where we actually cannot at compile know the type, and then you will always have to use implicit typing. Let's summarize what you have seen. Probably the most single important lesson here is that C# is a strongly typed language. C# will guard that types are respected. Some other
languages don't do this, it's definitely a plus when using C#. We have in this chapter looked mostly at using primitive data types, which are value types. For these primitive data types, C# comes with a number of keywords, such as int and bool. We've then also looked at the different options we have to convert data from one type to the other, and we've seen what different options exist for this. In the next chapter, we'll look at another very important data type, and that is strings.
Chapter 6 How to Create and Use Strings
Text, the written word, what would we be without it? On average, we see many thousands of words on a daily basis, in the newspaper, on our phone, on the bus that drives by, and, , also in applications. Words and written text are very common. It's quite understandable that a major programming language such as C# also has a lot on board to help us work effectively in our applications with text. First, we need to get to know the string type a bit since it is, well, different to what we've seen so far. Once we have gone through this short introduction, then we'll start working with strings. As said, strings behave differently ,and they will even amaze you at times. One of the interesting things about them in C# is that they are immutable. I'll explain what this means and why this is important for you as a C# developer to take this into account. Finally, as promised in the previous chapter, we'll take a look at how we can convert, or I should say parse, a snippet of text into another type. And let's dive in by taking a look in some more detail at strings and their specifics in C#. A string in C# consists out of a series of characters, chars really. Think of it as an array of characters. Together, they represent a piece of text. This can be a short text, even just a space or an empty string, or contain an entire book. When creating a string, as you'll see in a minute, the actual text is always surrounded with double quotes. This is different from a char, which we looked at in the previous chapter, which had single quotes. When creating a string in C#, the type that we get is the string, and you'll typically use the lowercase keyword as type to create a string. Just like we
already saw, the actual type is String, so with the capital. Lowercase string is, again, just an alias for the type, so the one with the uppercase. In this chapter, we'll take a look at using the many methods provided by the string type. You may remember from the previous chapter that C# has value types and reference types. The types we looked at in the previous chapter were indeed value types. Strings, however, are different as they are stored on the heap and thus are reference types really. However, most of the behavior of the string type is actually similar to a primitive type, as you'll see soon.
We'll explore later in the book in more detail what being a reference type really means in terms of the behavior of the string type. You can see I'm declaring a couple of strings. In the first line, I have used the string type, and I've created a string named s1, and I've initialized it with probably the most commonly used phrase in computer history, Hello world. Notice that the string is surrounded with double quotes, which indicates the start and the end of the string. A string can be empty. We could create a string using a pair of double quotes with really no content between them. That's an empty string. But as said, the string type contains many methods to enable us to work easily with strings, and one of these helper methods is string.Empty. This will create a new empty string and
assign it to s2. S3 is using something we've learned in the previous chapter, the var keyword. Can you remember what this was called? Indeed, it's called implicit typing. I create var s3, and on the right hand side I've put a string. C# will infer that s3 should be a string. A string will be stored on the heap, so the mass memory, because we can put a lot of data into it. So really, a string is pointing to a memory location. And if it's pointing to nowhere yet, its actual value will be null, nothing. It's pointing to nowhere. We'll discuss null and pointing to values on the heap later. S5 and, in fact, s4 are the same. When we create a new string like s5 and we don't point it to a string, it'll be null. Let us head to Visual Studio and create a few strings. So, let's create one, and it's actually pretty similar to what we've done so far. We start with the type, so that will be string. Then we specify the name of our string, so let's say that we want to create the first name of our employee. Then, on the side, we are going to create a string, and that needs to be delimited using double quotes, and let's set that. And the semicolon goes at the end. Let's create another one, and let's use lastName, and that's going to be Smith. So we create string variables using pretty much the same format here. We specify the type, string. In this case, I'm also using the lowercase string, which is also the keyword for the backing type, so string with an uppercase. Then I specify the name of the variable and then the actual string between double quotes. So I set this string as actually a shortcut for typing System.String. That is the actual string class. And I can also do a fullName. IntelliSense in Visual Studio is actually saying you could actually simplify this to be just string. And I must confess I almost never type System.String. We can also make a string without an initial value. I can say, for example, string noValueString = null. We'll come later to what null really means, but for now, it's enough to know that we have defined the variable of type string, so noValueString, but we haven't specified
an actual string as its value yet. And this will actually be the same as saying string s without specifying a value. This line actually also shows us something else. I can create a variable first and then somewhere in my code say that s equals the firstName. So I don't have to always assign a value. I can also assign it later on. And just like with any type, var, implicit typing, that is, can also be used to work with strings. So if I say, for example, var, let's say userName, I can also use a var for that, and because of the fact that I've specified a string on the side, C# will automatically know that this needs to be a string. Now that you know how to create strings, let's start exploring what we can do with them. Since a string is really a bit of an array, we can ask a string for its length. For that, we have the Length member, and it's actually a property.
Notice that I'm calling the Length on the string, and I have called myString. The returned value we store in an int. Next, I can also ask C# to convert a string to all uppercase characters by taking our string and calling ToUpper on it. This is a method. AgaBut note the difference. This one has brackets at the end. Similarly, we have ToLower, which will take all characters and replace them with their lowercase version. I can also ask C# if a string contains a certain other string that we pass in. Within the Contains method, indeed,
again, a method, I can ask if my string contains the string here Hello. The latter one, so the Hello, I'm passing in here as a parameter. This will return true if the exact string can be found. We can also ask C# to replace values in our string. Say that we want to replace all As by Bs. Well, then we'd use the Replace method, passing in the value we want to replace in a second argument, so b is the value we want to replace a with. This will return a new string with the replaced value, and that I'm assigning here to a new string, s. In the last snippet, I'm asking C# to take part of the string. And remember, strings behave like an array and thus are I'm asking here for the substring that starts with the second and ends with the fourth character since, , arrays are in C#. A frequently used operation on strings is concatenating variables, so taking one string and attaching another one to it, or another one and another one. You get the idea. It is so common that there are actually different ways to perform this task. The first option is very simple and probably the one you'll use most frequently. Here I have declared two strings, and now I'm using the plus operator to add them together. The result, so the concatenated string, is a new string, and that I store in s3, a new variable. We'll see soon that this has to do with string immutability. But for now, let's focus on the way we bring strings together. Notice that with the plus operator we can add as many strings together as we want. Another option is using another method called Concat, String.Concat to be exact. To this method, we pass the string variables, in this case, s1 and s2. Again, the result is a new string, s3, so the result is indeed the same thing. W the plus operator is, like I said, very commonly used, if we have a longer string, it can become a bit harder to read. Take a look here where I'm creating a greeting. I have an employeeName variable of type string and an integer age, and I want to create a greeting. And as you can see, I'm now using several concatenated strings, and I'm
including the variables in the code. Notice that I always have to close the string, write a plus sign, and then open it again. It works, but there are easier, say more elegant methods in C#. A first solution is using string.Format to string.Format and passing our, let's say, original string as the first argument. In this string though, notice I have now 0 and 1 between curly braces where I want to put the variable value. Secondly, I have the list of variables I want to substitute. The first one, so index 0, will be replacing the 0th parameter in the string. The second one will be placed where the 1 is written, and so the result is, , the same, but I find this much more readable. And there is another, even more elegant solution, which was added with C# 6, and it's called string interpolation. It works by adding a dollar sign in front of the string first. Note that it's outside of the string, so not between the double quotes. Then, in between curly braces, we now use the variable names instead of 0 and 1 we saw when using string.Format. At runtime, C# will replace the placeholders with their value, and again, the result is the same. We have already covered quite a lot of ground to learn about strings. Let's go and try this out in Visual Studio. I'm going to show you how we can work with strings, perform concatenation and string interpolation. And the string type is a very rich type when it comes to functionality. There's a lot of stuff built into the string type. Let's use that here. So, I'm starting here again with the firstName and the lastName, and I'm going to create the fullName. And I want this to be the concatenation, so the combination of firstName and lastName with a space in the middle. So, how can I do that? I can say firstName, and I have to have the plus operator. Then I want to include a space, as said, so that's another string, so delimited using double quotes. And then I do lastName, again with the plus sign. Now I have created my fullName to be the concatenated version of firstName, a space, and a lastName. This is a very commonly used
way to create concatenated strings. It is possible to also use the string.Concat. I can actually say that I create an empllyeeIdentification. That's a difficult word to type, but I think I need it correctly. And that would then be string.Concat, and then I can specify the argument strings that I want to concatenate in the string.Concat method, so that would be firstName and lastName. Again, while this works, I often don't use that. I typically will use the plus operator here. There's a lot of other options that we can do on the string. For example, I can say string empId, that's a shorter name of the variable, and I'm going to set that to firstName.ToLower. ToLower works on the string that we use this on, and it will convert it into the lowercase version of this string. Say that we want to use firstName, then the plus, then a dash, and then also the lastName.ToLower. That would then be the lowercase version of my first name and last name with a dash in the middle. And if ToLower exists, well, you may have guessed, ToUpper also exists. And let's put this back to lower.
A lot of these methods do support what is known as chaining. Let me explain. ToLower will actually return a string, a copy of the string in lowercase, but a lot of other methods that work on the string type also do this. For example, I could also say here .Trim. Trim will actually remove the trailing and leading white spaces around the string. That is also going to return a string, and that I then pass to ToLower. That makes sure that I then have a lowercase string without any spaces in front or behind it. We can also ask a string how long
it is. As said in the screenshots, you can see a string as really an array of characters, and that's in fact what I'm going to ask it. I'm going to ask how long are you dear string? So, for example, to this empId I can ask how long are you? This has a different symbol. This is actually a property that I can use to get that length of the string, so that does not require any brackets behind it. If we run this, we'll see what these values are. So we see that fullName indeed is the concatenated version, and then we also see employeeIdentification, which didn't have a space, but that was intentional. We have empId and we also have the length, and it was 13. I assume that's correct.
We're far from done with the string type. There's a lot of other things we can do. We can ask fullName, that was this version here, if it contains beth or Beth with an uppercase. If that's the case, then we are going to write something to the console, W we're at it, and before I run things again, I'm also going to do something else with my string. I'm going to take the substring. In this case, I specify where we need to start. C# is zero base, so this will actually start at the second character. And then I ask to take a substring of three characters long that I put in this substring here. Let's run this. So this does show me that it's Bethany, and characters 2 to 4 indeed are eth. Now finally, I want to show you one more way to concatenate strings, and that is string interpolation, and it's in fact my favorite way of doing it. It was added with later versions of C#. Let me show you string interpolation.
With string interpolation, I can actually type a dollar sign and then I surround the string again using double quotes. Inside of this string, I can now use the names of the variables I want to include in my string between curly braces. So if I say, for example, here that I want to again create this version of my concatenated string, I do it like so. I say firstName, I set it between the curly braces. Then I add my dash here without having to open and close my string.
And then I do the same for lastName. Now this is doing the same as this, but without the ToLower and the Trim in this case, but I could even add that. But this is, in my opinion, a lot cleaner. Definitely if you have longer concatenated strings, string interpolation will help you quite a lot.
Chapter 7 Escaping Text Basics
The strings we have used so far, well, are pretty basic. They start and end with a double quote, and in between we've just had some regular characters. But what if you want to include, say, a new line, for example? Or what if we want or string to include a double quote as part of the string? Surely that won't work with what we have looked at so far in it.
For that purpose, C# can contain escape characters, which is in fact a combination of a backslash and a character, and together they have a special meaning. Sometimes they're also referred to as an escape sequence. Take a look at the string we have in the snippet here. I want to show part of the string, so the name of the employee, on a new line. This I can achieve using an escape character, \n. Between the first name and the last name, there's a \t that is another escape character. This one will add a tab. In the demonstration, we will use these and other escape characters, but first, one more handy trick with strings.
Say that we want to represent a file path in a C# string. A file path contains backslashes itself, typically. We just learned that the
backslash was actually a special character that is used to say to C#, now comes an escape character C#. The next character should be treated in a special way. To represent a file path with backslashes, we therefore need to escape the backslash itself, and this we can do by adding a double backslash. The first one says now comes an escape character, and the second one is the one actually being escaped, so this one will actually be shown, and you see the result here on the screenshot. W that works, I reckon it's not that easy to read. It's definitely not in a more complex file path. In general, strings that contain a lot of escape characters tend to have this. All this escaping makes it harder to read. There's a way to say to C#, well, for this string just ignore any escape characters, so don't see the backslash as the start of an escape sequence, just use it as a regular character.
That's where verbatim strings come in. If we prefix the string with an @ sign, C# won't see the backslashes as something special. It will just ignore them. Take a look at the second line. Don't you agree this looks a lot cleaner? We have new things to try. Let's go back to Visual Studio and bring in escape characters, and then we'll also give verbatim strings a try. Say that I want to create a displayName, and I set this, again, using string interpolation to the following. I want to create a welcome text, but now I want to in my console also add some text on a new line. That I can do by including an escape character, \n. This will create a new line. Then I'm going to, using string interpolation, specify the firstName. Then, I want to include a tab that's also an escape character, \t, and then I use the lastName. Let's write that to the console.
So now we see, indeed, that a new line was started, and, while the tab is a little harder to see, but it's actually between Bethany and Smith. Now, what if I, in my string, also need to include a backslash? Say that we want to have a list of all employees and they are in an Excel file, and I want to represent the path to the Excel file on my disk and a file path typically contains backslashes. Well, how are we going to do that? Well, let's take a look.
Say that my file is in this location here. I'm creating a string to represent that file path. Immediately you'll see that because I have inside of my string slashes that C# is complaining. It says, well, \d and \e are escape sequences. I don't know because it is seeing this slash out of my file path as an escape character. I need to let C# know, well, don't treat this \d as an escape sequence. No, instead I want you to use the backslash as part of the string. So I'm going to escape by escape backslash. And I have to do the same thing here. Now I actually have a path, a file path, that I can work with within my application. C# now knows that I'm actually using backslash as part of my string. Now what if I have a string in which I also use a quote? Because quotes typically open and close the string. I can also escape the quote. That's what I'm doing here. If I want to put between quotes "best pies," I can put a quote, but I can escape it, as you can see here.
Now we can also with these more complex strings use what is known as a verbatim string. I can use, again, the original invalidFilePath, but now I put an @ sign in front of it, and then I don't have to escape my backslash. This verbatim string basically says don't use escape characters at all in this string. We're doing well on a trip to learn about strings. One thing we haven't done is testing strings for equality. Let us look at that next.
In C#, we can compare two strings for equality using, well, again, a double equals sign. Take a look at the snippet here on the screenshot. I have a string Bethany with a capital B. I can check, and that's what I'm doing in the second line, if firstName equals Bethany, again, with a capital. When we run this, this will return true. So we've used the double equals sign here. If we, in the third
line to this, compare with lowercase bethany, well, by default, this will return false. I say by default since that is what C# will do. It will compare each character in the string, and when they're not exactly the same, it'll return false. And the double equals is actually overloaded. We'll learn later what overloaded means. It's exactly the same in C# to use the equals method on a string, passing in the string you want to compare against. So both methods are used and do, in fact, the exact same thing. They just compare the value. Say that you now want to compare a string, but ignore the casing. There are, again, different ways to do so, but here's one way I typically use. What I'm doing here is I'm making the two strings I'm comparing all uppercase.
Then, no matter what the casing is, you're comparing the exact same values. The same would work with ToLower. Let's see how we can compare strings in C# in action. So let's compare strings. I've created two name strings here, name1 with uppercase B and all uppercase name2. Let's do a few equality tests.
I'm going to check if name1 is equal to name2, checking if one variable is equal to another one. So a regular equality check we do in C# using a double equals sign, and that's the same here for
strings. So you see me using here a double equals sign. This operation will return a Boolean, and you may already guess that the result of this will be false. So what I did here was an equality check, and C# will do a comparison, and it will also take into account, by default, whether or not that character is lowercase or uppercase. So in this case, these two strings are not equal then. If I'm going to check if name1 is equal to another string Bethany, and let's put this one in comment, the result is indeed True. So although I'm comparing two different strings, they are equal. In this case, I'm now checking if name2 is equal to uppercase Bethany. But in this case, I'm not using the double equals sign; I'm using the .Equals met.
The result is the same indeed because the double equals sign, as well as the .Equals method do the same thing. If I want to do a case insensitive comparison, I can again bring in the ToLower or ToUpper. In that case, I just do a ToLower or ToUpper on both sides, and then I actually can compare without taking into account the casing. And in this case, I'm going to check if they want ToLower is equal to bethany, also lowercase. And that's, , going to result in True. Say that we have a string that contains the word Hello. I then create a new string, b, and I set it to a on the next line. Then in the next line, I'm adding to b using +=, space, world. Then, we asked to see what a is.
The result is Hello. What is going on here? In the previous chapter, we learned about primitive types, and as mentioned already, these variables contain their own value. These are value type. Strings are different. They're reference type. They point to the actual string in memory. I'm going to show you in the diagram what has happened, and then this will all become a lot clearer. So first we create a, and a will contain Hello. A points to the string Hello. That's important here. Now we set be equal to a. B doesn't contain a copy. It just points to the same memory location as a. We create a new string, space, world, and then I set b to the concatenated version of what it currently contains plus the new string. Notice what happened now. B, and b only, now points to the newly created string. A, well, doesn't know at all about what happened. It is just happily still pointing to the original string Hello. Now this is all happening because strings are reference types.
They point to memory locations in C#. They aren't created on the stack. Rather, they are created on the heap. We'll learn more about
reference types and value types throughout this book. Don't worry if this is not yet crystal clear. There is another thing to note here. When we created this new string, space, world and suffixed b with it, an entirely new string was created. The original wasn't changed. This is because strings are immutable. Once created, it cannot be changed. Now you may be thinking, hey, we saw earlier that we could, for example, replace a character within a string with another one. Surely we are changing the original string. No? Well, I must disappoint you. Every operation you do on a string will result in a new one being created. So, also, your replace operation will return a new string with the values replaced. Why am I telling you this? It this important? Well, yes and no. No because, in fact, C# handles all of this for you. It'll return in new string, and, if needed, it'll clean up the old string for you too. But doesn't all this copying and creating of new strings have an impact on performance? Well, that would, or maybe I should say could, be the case. In regular circumstances, by no means do you really need to worry. C# will handle this for you. But say you are doing string operations inside the loop or you're doing a lot of concatenation operations. It could result in a lot of strings being copied over and have a negative impact on performance for your application. Time to start worrying? Absolutely not. C# has a solution on board, and it's called the StringBuilder. As its name is giving away, it's used to build strings, but in a more performant way, without all the copying. So if you find yourself in a situation where you need to concatenate a lot of strings for which each individual operation would result in a string being copied, the StringBuilder will be worth its name in gold.
Take a look at the snippet you see here on the screenshot. I'm creating a new StringBuilder. Don't worry about this new thing here. That's the constructor again, and we'll learn about it later. Then, on my stringBuilder, I'm calling the Append method to append the string. I'm doing it a few times using AppendLine, which will append the string I'm passing in on a new line. Using the StringBuilder, none of this will result in the string being copied over multiple times, which would be the case for regular strings. Finally, I can ask the StringBuilder to give me back, well, the actual string value, and so we can do that using the ToString method on our stringBuilder class.
Chapter 8 Strings are Immutable
Time to head back to Visual Studio and make sure we understand string immutability and the StringBuilder. So far, we have been using strings, and without knowing, we've actually already come across string immutability. Because all these methods that we've used so far, like ToLower, they actually don't work on the original string. They return as a copy of the original string. Well, let me show you that, that strings are immutable, and that whatever we do, we are working on a different string. Let's say that I create a string name again, and I set that equal to Bethany. I create another variable, let's call it anotherName, and I set that equal to name. So now I have two variables, name and anotherName, and they're both pointing to a string, Bethany.
Strings are reference types. I don't expect you to already fully understand reference types, but it actually means that the string Bethany is stored somewhere on the heap, so on the mass memory. And I have two "pointers" that point towards that string, name and
anotherName. And if I say that to my name string, attach another string, and I'm going to use this increment operator here, which is going to extend the original string with, in this case, let's say Smith. Let's put a space in front of that. What I've done is I've actually created another string Smith, and I've attached that to Bethany, but I've not attached it to the original name string. No, in fact, a new string was created, and name is now pointing at a newly created string. Let's take a look at the output if I now write Name and anotherName to the console. Let's see what happens.
Name is now pointing to the concatenated string, Bethany Smith, but another name still says it's just Bethany. That is correct. Since I had set anotherName equal to name, and that means that they were originally pointing both to Bethany. The name variable has changed. It's pointing to the concatenated new string, but anotherName still points to the original string, Bethany, which was left untouched. The string immutability is something that will always happen as set. If I, for example, say that I want to have the lowercase name, and I say, well, take name and do it ToLower, now this is not going to touch the original string, it's going to return us a copy of the original string with every character converted to its lowercase counterpart. The original string, Name, is left untouched. We're always getting back a
copy. Because of this string immutability, some operations will actually result in a lot of strings being created, and that can actually be bad for memory consumption in our application. Say that I have this very simple loop here in which I'm going to loop 2500 times. I'm going to always concatenate to index, which is originally an empty string, the value of i. Now, by doing this this way, we're going to create 2500 copies of that string. Every time we attach to the original string, we're going to get back a new string. The original string is left untouched every time.
While I say that this can actually use more memory, don't start panicking. This is actually still the typical way of working with strings. But if you have to do a lot of concatenation within your application, say, for example, in a loop, using StringBuilder could actually be beneficial. Let's use the StringBuilder. I'm going to create a new StringBuilder. And when you start typing the word StringBuilder, you will actually see that Visual Studio gives you a word squiggly. It doesn't know about StringBuilder. That is because it lives in a different namespace and you have to bring that in. You can actually hover over StringBuilder, and then Visual Studio will give you some help, and it says you should actually bring in a namespace called System.Text. Well, let's agree for now, and we'll talk about
namespaces later in this book. So now I have support for StringBuilder.
Let's called this builder and create a new StringBuilder. This is again using that constructor syntax that I've mentioned before. We'll look at constructors in much detail later on. On that StringBuilder, we have a lot of options to attach to the original string, and this will not actually always create a copy of new string, that is. It will work on the original string, and it is for memory consumption, in any case, a lot better. So I can, for example, say that I want to append to the string that is being built by the StringBuilder. Last name:, and then I do another builder.Append, and then I say now include lastName, and I continue on this path for the firstName. And let's put a space in here as well. And now I actually want to get the result. So now I can actually say that I have my final result, and I create for that an actual regular string, and I say that I want to get out of the StringBuilder the actual string value. That is, the concatenated
version. Let's take a look at the result, and let's comment out these lines here. And now indeed see that everything is on one line.
That's because I've used append. I should have actually used AppendLine, and this will include a line break at the end. We looked at converting from one type to another. Now with strings, we can actually do more, and we can actually parse a string value into another type. Let's take a look. Say that we allow the user to enter a value for the wage of an employee using our console application. The captured value will always be, indeed, a string.
And we'll probably want to do some calculations with that entered value, and so we need to create, perhaps, a double from the entered value. So we need to convert it from a string to a double. We're going to parse the string into a double here, and this we can do using the Parse method on double. If the value can be parsed and it
can be read into a double, wage will now contain the double equivalent of the entered string. Not only offers this capability. For example, we can do the same thing on a bool, as you can see in the last line. Now it might be that the user has entered an incorrect value, one that cannot be parsed into, for example, a bool. For that reason, we have the TryParse method. So if we are unsure if the parsing will succeed, it's best to go with TryParse and let C# first try the parsing. If all goes well, the value will be available in the variable passed to TryParse, so that would be the b parameter here. I'll explain what the out is doing here in the next chapter. TryParse is your safety net. Keep in mind that calling parse on a value that cannot be parsed into the requested type will cause a runtime exception. That, you will not have with TryParse. Let's play with parsing and also bringing support for TryParse. Say that we have the following. We are asking the user to enter the wage so we can then use it in calculations later on, and I'm doing that using Console.ReadLine.
Now the input is going to come in as a string. Now I can't use strings in calculations. I actually need types such as integer or double. What I now can do is parse that value. I'm going to create an integer here, and I'm going to call it wageValue, and I'm going to say that I want to let C# parse the value using the int.Parse, again,
a method on the int type, on the integer type that is, and I'm going to pass it the wage string.
This is going to try and parse that value entered by the user, and it's going to try putting that inside of this wageValue. Let's see this in action. I specify the value to be 1234, I hit Enter, and now wage is, the string 1234, and wageValue is an integer, 1234. C# has now parsed string input into an integer. If I would have entered abc, things would have gone wrong because the parsing would simply have failed. For that reason, we are typically going to use TryParse. Using int.TryParse, we're going to ask C#, well, here's the value; try parsing that, in this case, into this integer value here. And it does require the use of the out keyword here, which I'll come to in the next chapter. What you need to look at at this point is that I'm now passing in that original string, and I'm going to see if that value can actually be parsed. I'm going to ask C# to try that, and if that works, well, then I show this string, otherwise, I show Parsing has failed. Let's run it, and let's now try entering abc, which is definitely impossible to parse into an integer. And indeed, we see that parsing
has failed. Now parsing can actually be done for other types as well. For example, for datetimes. Say that I have entered in the UI this date to hire an employee, the 12th of December. I'm then going to use the DateTime.Parse, passing in that string, and then C# is going to try parsing that into a valid date. And that seems to have worked. It has parsed that value successfully. I hope you understood the importance of strings in C# and also that they are different. They are reference types and thus stored on the heap. Strings are also immutable, meaning they cannot be changed. When we do something on just any string, it'll result in a new one being created and returned. This can, in some cases, result in a lot of copying of string values, and that's where the StringBuilder came in. And I mentioned the concept of methods, ones that is, already quite a few times. In the next chapter, we will start working with methods in C#.
Chapter 9 How to Work with Methods
Writing all of our code in one large block won't be very readable when building applications. It might be okay for a small demonstration, but once we're past that stage, there are definitely better ways, as in this chapter, Understand Methods in C#, a way to group and reuse code statements. First, I'm going to give you a brief overview of methods and show you the different options we have with them. Without really paying any attention, we have already used quite a few methods, and that will become clear soon enough. Next, we need to spend some time looking at the different options we have to pass data to our methods using parameters, and C# is offering us different flavors. Finally, in the last topic, I'll be spending some time looking at some miscellaneous features, again, related to methods and parameters. The first topic on the menu was understanding methods, so let's start with that. Methods are extremely common to use in C#. They are code blocks which contain a number of code statements. They have a name, and by calling that name, we can invoke a method and thus the code that lives inside of that method. Methods can be any size. They can contain just a single line or contain a whole bunch of statements. Frequently, methods will define that they require one or more parameters to run, and thus when we call the method, we need to pass in a value for these parameters. Methods can be used to define all sorts of functionality, and based on that, they can optionally also return a result to their caller. By placing our code in methods and thus not creating a long list of statements, we definitely improve our code's
readability. But not only that, methods will also help with code reuse. When creating applications, we'll often, well, do the same thing. By putting this functionality inside a method, you can invoke that method from different places within your application, hence improving code reuse. Finally, methods always need to be declared inside a class or a struct. We'll talk a lot more about classes and structs soon. For now, keep in mind that you'll always need to place a method between the opening and closing curly braces of a class or a struct. And every executed code instruction in C# itself will happen in the context of a method. Methods are always created with pretty much the same structure. In general, you'll always see the same items coming back when defining a method. On this screenshot, I have captured the general structure of a method. A method has to have a name and requires two brackets behind its name.
Then the actual method statements, so the body of the method, goes between two curly braces. Essentially, the items in orange are required. In fact, what is also required is the return type, which indicates what type of result will be returned from that method. It's possible that the method isn't returning anything, and we still need to indicate this with a void return type. A method can optionally also define it requires one or more parameters. These need to go between the brackets. If there aren't any, we still need to add the brackets but then empty. Finally, it can optionally also have an access modifier defined. We'll talk more about access modifiers later. For
now, remember that this can be used to indicate from where this method can be invoked, so for example, only from within this cause or publicly. All of this together is what is referred to as the method signature. Here's the first method. They almost don't come any simpler.
The method's name is AddTwoNumbers. It has an int return type, and it's publicly available. Pretty simple, isn't it? Now a method named AddTwoNumbers will probably need two parameters. That's what I'm going to add next. Int a and b are the parameters. Now the method signature defines that when someone wants to call this method, they will need to pass in two integer values, which will go in the a and b variables here.
Just to be sure, notice that we need to specify here the type of parameters, so the int before the a and b. If you would try to compile the code at this point, it would actually give us a compile time error. Any idea why? Well, the answer would be the method has a return type specified, the int here. When doing so, the method must do what it says. It should actually return an integer value at
the end of the method body. With this line now added, return a + b, this will be the case, and the compiler is happy again.
Return is a C# keyword that essentially stops the execution of the method and returns the value to the caller. When a method defines it's returning an int, all code execution paths should do this. Again, otherwise, you will get a compile time error. Say that we include an if statement.
There's a possibility that no value is being returned from the method. C#'s compiler won't allow this to go to through, and it will raise an error. You should also include code here that returns a value for the case that a is less than b. But one if the code in your method just performs a task, show text on the console, for example. What would it then return? Well, great question. It's not required for a method to return anything. The only thing C# requires is that we then indicate this. For this we have the void return type. This is an
indication that the caller shouldn't be expecting anything to come back from the method, and the method now also doesn't include a return statement. Once we have a method defined, we can also invoke it. At this point, I'm going to invoke the method from our main method which we have used so far.
Actually, without knowing, main is a method too, but a special one known to C#. When you run your application by default, this main method is invoked. If you have that DisplaySum method, so the void method I just showed you, we can now invoke it by calling DisplaySum and then between brackets, we now need to pass in two values of type int, since that was in the method signature. I do want to point out the static keyword here, which I'll come to in the demonstrations. Our DisplaySum had the void return type, so it's not returning anything to the caller.
That's different for the AddTwoNumbers method, which was returning us the sum of the two parameters as an integer value. So we can assign the returned value of invoking the AddTwoNumbers method to a new variable, here, result. Result will contain after execution the value 99 and can be used locally. We already have a basic understanding of methods, so we'll use that to create a few methods. I'll show you how we can add parameters and return a value. , just creating a method won't do a lot. We also need to invoke them, and I'll show you how we can do that here, paying extra attention to the static keyword. So up until now, I've written the functionality, my code, really, in a static void main. While that works, it wont be a viable way to write a application. We'll start breaking up our application code into a couple of methods.
I may want to calculate the wage of someone, of one of my staff members. Well, instead of doing that directly in this main, I'm going to do that in a method, because I may want to call that functionality from multiple places within my application. And then I can't do it if the code lives in this main. I'm going to actually have to do that in a method, and then that can be invoked from pretty much everywhere within my application. So I'm going to go outside of the static void main, and in here I'm going to create my first method, and it's going to be the CalculateYearlyWage method. So what I'm
going to do is I'm going to write first the access modifier. It's going to be a public method. I need to bring in the static keyword here as well. I'll explain you why I need a static here. It is because I need to invoke this from my static void main, but, again, I'll come to that later on. For now I'm going to also give this method the return type void, because at this point I'm not going to have it return anything just yet. So void here is the return type. That is what the caller eventually can get back from this method. And if I don't need to return anything, I'm going to make it void.
The method that want to create is going to be called CalculateYearlyWage, and I need to suffix that with two brackets. And optionally, I can put parameters in here. And then the actual body of the method, so the functionality that we're going to write in here, is going to live between these curly braces. I do need these brackets; they always need to be there even if I don't have parameters. My method is going to be calculating the yearly wage, so I need to know what is the monthly wage, as well as the number of months our employee has worked for us. So, what I'm going to do here is
I'm going to bring in the monthlywage as a parameter. Notice what I'm doing here. I'm writing here first again the type of the parameter and then the name of that parameter. And I'm going to do the same thing, but now for the numberOfMonthsWorked. So now my method has two parameters. I can within this method use these two parameters. They are known inside this method but not outside of it. The method block is the scope for these parameters to be known. So for now, I'm going to keep things simple. I'm just going to write to the console the product of the monthlywage times the numberOfMonthsWorked. So now I have these methods. Just because it's standing there, it's not going to be executed. Actually, I can now from my main call invoke, let's say, this method. How I'm going to do that? Well, first I need to prepare some values to pass to my method. So let's say that the monthly wage is $1234, and the months is 12. So those are local variables of my static void main. They are not known automatically in the CalculateYearlyWage method. We need to pass them, and they will be passed as arguments to our method. There they will come in as the parameters of the method. So let us now invoke our method. I'm going to call CalculateYearlyWage, and now Visual Studio will suggest that when I call the method I need to pass in two arguments, so concrete values for the parameters defined on the method signature. The types of the arguments must be compatible with the type of the parameters, so that's going to be monthlywage as the first argument and then months as the second one. How will this now be executed? Well, what I'm going to do is I'm going to put a breakpoint here, and I'm going to put a breakpoint here as well, and here as well. Let's run our application, and then we'll see the order in which these actions are executed. You see that we first, hit the CalculateYearlyWage. Let me jump to our CalculateYearlyWage method, and then we arrive here in the Console.ReadLine. That is the order of execution that has
taken place here. And we see the result. Just calling a method and not being able to do something with the result might actually not prove very worthy. In many cases, we are going to have our method actually return a value. Instead of making the return type here void, I'm going to make this int. But this is now saying to C#, that this method is going to return an integer value. You see that Visual Studio starts complaining, it says, well, you're not returning this value, so I need to return here a value of type integer, and that's going to be, again, the product of monthlywage times the numberOfMonthsWorked.
This method is now updated, it's going to return an integer value, and I can now capture that, so I'm going to say here, set the int yearlyWage equal to the value returned by invoking that method, and it is now the int yearlyWage. So the value of this integer variable is going to be filled up by getting back that result from our CalculateYearlyWage method. And as I said, we can actually use that value here in our static void main, so I can now, for example, also write to the console that yearly wage, but it's now coming from the execution of the main method. We're now seeing here that the wage
is now being returned from the main method. Methods can do a lot more than just containing one line of code. They will typically be quite a lot bigger. So just as a small example, so what I'm doing here is I'm going to check if the numberOfMonthsWorked equals 12, and then I'm going to pay an extra month to my employee, so I'm going to increase the numberOfMonthsWorked with 1.
And if not, well, then I return the original value. So what you see here is just that we can include more logic inside of our methods.
Chapter 10 How to Find the Correct Method
We have already taken a first look at how we can pass data to our methods using parameters, but there are quite a few extra options to do this, so let's look at that next. Have seen that a method requires a name, a return type, and 0 or more parameters. When we ask C# to invoke a certain method, it'll start searching for the corresponding one. To search for the correct method, it doesn't just use the name. No, indeed a few things are considered to locate the correct method. The method name is, the first one. Then it'll also see if the call to the method is passing the same argument types as the method requires. The method may say it needs a string parameter, but if we try to invoke it passing an integer argument, C# won't match it. It is a good thing. It's a type safety of C# that will make sure at compile time that not only the name matches, but also the parameter types. Also, the number of parameters is taken into consideration when searching for the correct methods. In conclusion, we can say that three things are used to find the correct method, the method name, the parameter types, and the number of parameters. Only when this combination is found, C# will invoke the method. It also brings another thing here. It's this combination that needs to be unique, not just the name. Indeed, we can have multiple methods with the same name. As long as the combination stays unique within the class, we're good. The fact that multiple methods with the same name can exist within a class is called method overloading.
The name can be the same, but either we need to define a different number of parameters or different parameter types or a combination of these two. I have two methods with the same name. Both are called DisplaySum. The first variation has to integer parameters.
The second one has three. C# will accept this. These are now overloaded methods. And then now we invoke from the main DisplaySum, as you can see here, with just two parameters. The highlighted version is selected. C# will match this method, and this one only here, because of the number of parameters matching. Let us head back to Visual Studio and take a look at method overloading in action. When we asked C# to invoke the CalculateYearlyWage method, it actually searched for a method that
had that particular name, and it also had the same amount of parameters, and also the type of parameters needs to match. It is possible to actually create overloaded methods, methods with the same name, but then they need to contain either a different number of parameters or a different type of parameters. And that still gives C# the ability to distinguish which method you want to use. So let me show you method overloading for this.
I'm going to bring in a second method with the same name. As you can see here, that's also CalculateYearlyWage. I also accept monthlyWage, and numberOfMonthsWorked, but now this one accepts a third parameter, bonus. The types of the parameters are the same, so we cannot just do this and have Visual Studio say, well, I already have a method with these parameters, so you cannot do that.
I cannot distinguish between these two. But that's not what I was planning on doing; I actually added the third parameter, and that's okay for C#. So now just including a third parameter of type integer, in this case, bonus, and I used that inside of my method. So these two methods are not overloaded methods. They have the same name but a different number of parameters. But I can also do the same thing, but the difference now is that the parameter types are different, so they're now doubles.
So at this point, C# can still distinguish between these three methods based on the parameters, the number of parameters, or the types of parameters that I'm passing it. So I'm going to go here, and I'm going to bring in a bonus. Let's set that to 1000. I'm going to change the method invocation as we already had, and I'm going to add bonus. You see automatically that Visual Studio is showing me these overloads.
These are different overloads of the same method but with different parameter combinations, as you can see here. So what is now going to happen? Well, I'm going to show you which is actually invoked, so I'll put a couple of breakpoints, and then we'll run the application and we'll see which one gets hit.
And you see that indeed the one with integer parameter types is being selected, is being used, and that's because I passed integer values over here. Now let me bring in a little bit of extra code here. I've now brought in double variants of our original parameters, and I'm now going to also calculate the yearly wage but now passing in these double families. Can you guess which one will be called? I think you do. Let's take a look. Indeed, based on the parameter types and also the number of parameters, C# has decided that this method needs to be invoked.
Chapter 11 How to Pass Parameters by Value & Reference
In C#, it's possible to pass parameters to a method in two ways. So far, we've actually used the default, and that was by value. The second option is passing the parameters by reference, which will require us to use a new keyword, the ref keyword, on the parameters we want to pass this way. Let's explore these two options in more detail. As said, the default way of passing parameters is by value, and that's what we've used so far without giving it any attention, really. With this approach, when calling a method and passing it arguments, so values for the parameters, a copy is created for the method to work with. The method can do whatever it wants on these values. It's a working copy of the values, and thus the values in the caller aren't changed. They are not being touched. Say we have our AddTwoNumbers method again, and I have defined a to be 33 and b to be 44.
I'm passing these to the AddTwoNumbers method. Essentially, a copy of these values is created, and that is passed to the method. If the method now changes the values, so here we're adding 10 to the b parameter, the original value of b, so in the caller, is not influenced. It keeps its original value, 44. The method works on a copy. That is passing by value. The second option to pass them is passing by reference.
As the name gives away, we aren't passing a copy; instead, we're passing a reference to the original value, so the value that lives from where the method is invoked. No copy is made in this case. That has a big influence on the behavior. If we now make a change on the parameter value in the called method, the original will also be changed as well, since, indeed, we have passed a reference to it. For this to happen, we need to include the ref keyword, saying to C# that you want to pass by reference. So passing by reference is definitely intentional. You'll want to do this when the method actually should work on the original values. Notice I have now added the ref keyword, and I've added it in two places. In the method declaration I've now added the ref keyword before the int b. Our method can only still be called by passing in b by reference. When doing this, so when we call the method, we now also need to specify the ref keyword here. Executing the method, which does add 10 to the current value of b, will now have an effect on the value in the caller. Normally, a method can just return a single value. Using ref, it's also possible to let a method work on different values and change the original values of the parameters. One more thing, before the method is called, all ref parameters must be initialized. In the next demonstration, I'm going to show you the difference between calling by value and by ref. First we'll see that our default way of working
doesn't change the original values, so that's using by val. Then we'll use by ref parameters and see that this will change the values in the caller. From now on, instead of doing everything from the main, I'm actually going to use separate methods and invoke those from the main, and they will in turn invoke other methods. That's a really normal way of working, actually.
I've prepared things already a little bit. As you can see, I've created this method using value parameters, which I'm invoking from my main. It's a void method, so it's not returning anything. In this method, I'm going to show you how we can pass parameters by value. You'll see very little difference with what we have done so far. In fact, we have already used passing of parameters by value. So I again have a number of local variables, and I then have the CalculateYearlyWage method again. I've changed it a little bit, so it's again receiving monthlywage, numberOfMonthsWorked, and bonus,
but now notice what it's doing. If the monthly wage is lower than 2000, I'm not only going to increase the bonus; I'm going to double it, in fact. That's a nice bonus. Then I will write to the console what the yearly wage is, including the bonus, and I will also print out the bonus. And finally, I will return that same value. I'm invoking this method twice. CalculateYearlyWage is involved here and here. Why am I doing that here? Well, I want to show you that inside of the CalculateYearlyWage method, I'm actually going to change the value of bonus, but it's going to be a local copy of that value bonus that's going to be changed. Because otherwise, after running our method the first time, for the second employee, that bonus would have already changed. The value of bonus is initially 300 and that I'm passing to my CalculateYearlyWage method. I then see that the monthly wage is lower than 2000, so I double the bonus. And that's what you see here. And that bonus value that is changed is local to the CalculateYearlyWage method. So when I run the method again, again a new copy of bonus, the original value of 300, is being passed in. This is what happens when you pass parameters by value, that is, the default. We've actually used that already up until now. So when we pass parameters by value, we're passing a copy. And whatever you do inside the method doesn't impact the original value. When I actually pass by reference, things will change. Let me show you. I've now brought in UsingRefParameters, and I will call that from my main. Take a look at the difference now. It's pretty much the same, but there's a big difference. In fact, you can notice it here in the parameter list of my CalculateYearlyWageByRef method. The ByRef is, to distinguish it.
Notice that I now have a parameter, the bonus parameter, which does have the ref keyword in front of it. That ref keyword will actually cause this parameter to be passed in as a reference. We are getting in in our method a reference to the original value, so the value in the caller. And inside the method I'm again checking if the monthly wage is lower than 2000, and if that is the case, I'm doubling the bonus. But that bonus is now a reference to the original value. So the original value is now changed from a method. When I call the method, I also need to add that ref keyword here, so I need to add the ref keyword both in the arguments list and in the parameters list in the method declaration. We see indeed that the bonus was doubled already the first time, but now the value of 300 is overwritten in the original method. That's what we see here. Indeed, the bonus value is changed here, and our second employee, well, he's having a lucky day, because his bonus should have been
300. His monthly wage is actually 2000, so it shouldn't have been doubled, but the value that was passed into a method had already been doubled. I need to initialize the variable that I'm passing in by reference with the value in the caller.
So before I invoke my method, bonus needs to have received a value. I cannot pass it in without the value. Now we see indeed here that the C# compiler is complaining. I cannot pass in an un initialized variable by reference.
Chapter 12 How to Use out Keyword
Next to ref, there's another keyword in this area, and that is the out keyword. It's pretty similar, in that it's also passing by reference.
The ref required us to ensure that the variable was initialized. Using out, however, that's not the case. We can actually pass in an uninitialized variable, and the requirement with out is that before the method now exits, the out parameter must be initialized. Since it's also by reference, the original value, if any, can also be changed from within the method. W this works very similar to ref, you use out frequently when you want your method to return multiple values to the caller. By default, only a single value can be returned from a method. Using the out keyword, it becomes possible to return multiple values. And yes, that worked with ref too. But remember, using ref, the parameters must be initialized. Let us head back to Visual Studio and see how the out keyword works exactly. Let me now show you how we can use the out keyword in combination with parameters. This will allow me to also use a parameter by reference,
but it doesn't have to be initialized now. I can actually use out to return multiple values to the caller. I'm going to call here the UsingOutParameters method, which you can see here.
Notice first that inside of this method I have, again, the bonus value but now it doesn't have an initial value like we had when we used ref parameters. Then take a look at this method here, the CalculateYearlyWageWithOut. And notice now that the bonus parameter is prefixed with the out keyword. Inside of the method, I'm going to change the value of bonus. I'm going to randomize the value first, and then I'm going to check if the value is lower than 500, and I'm going to double the bonus, and I'm going to write something to the console. But in the end, I'm going to return the value of the yearly wage. I've also, inside of this method, set the value of the bonus parameter, and that is also going to be returned
to the caller. Because I have added here the out keyword, that value will also change the original value of bonus over here. So out is also passing by reference, but it doesn't have to be initialized. I can actually initialize the value inside of my invoked method, and that's what I'm doing here. So the value is going to be changed also in here in the caller. Notice that also in the caller I need to specify the out keyword, and then the value here will be changed. Let's run the application. So I've run our method. The yearly wage is printed, although I haven't specified the value for bonus over here, it does get value in the caller. That is because it was set in our invoke method. And then that value is returned to the caller using the out keyword. So we are indeed returning multiple values to the caller, and if you want you can have as many parameters that you want prefixed with out. In the last part of this chapter, we're going to look at a few miscellaneous topics around methods and their parameters. First, I'm going to show you the params keyword. Then we'll take a look at optional parameters and named arguments. Finally, I'll show you syntax, which was introduced with C# 6. So first, the params keyword. What's this all about?
So far, the number of arguments we've passed to the method, so when calling it, was the same as the number of parameters defined on the method signature. That's what I said you had to do. That's still correct, and it's the default. But we can bring in the params keyword, and that will accept a variable number of arguments. In other words, once we add the params keyword on the method parameter, all method arguments, so in the caller, will be passed to the params parameter. For this reason, the parameter on which we add this must be an array. Take a look at the snippet here. You can see the params keyword has been added on the values parameter, and that is of type integer array. We can now call the method with 0, 1, 2, or even 20 arguments. All values will be stored in the params parameter, and inside the method I can, now, for example, loop over that array. So instead of creating several overloaded methods, each with a different number of integer parameters, we've now solved this with using the params keywords. You can use more than one parameter but only one can contain the params keyword. And if you do have more than one parameter, the one with params should always be the last one. Otherwise, C# can't figure out how many arguments should actually go in the params one. It’s now time to take a look at the params keyword in action. I'm going to add params and then show you how we can invoke the method at hand with a variable number of parameters. If we don't know beforehand how many arguments a caller is going to pass to our method, we can actually use the params keyword. Say that we want to calculate the average of a number of wages, and I want to do that in my CalculateAverageWage method. Instead of creating multiple overloads with 1, with 2, with 3, with maybe 50 parameters, I can actually create just 1 method and use the params keywords.
And this params keyword needs to be used in combination with an array. In this case, I'm going to pass it integer values so I set the type of the parameter to be an array of integer values. This allows me to call my method with just any number of parameters, and all these values, all these arguments, will be placed inside of this array. That's what you see here. I'm calling CalculateAverageWage, and I pass it monthlyWage 1, 2, 3, and 4. So as said, the parameter I use in combination with the params keyword needs to be an array, and that allows me to, inside of my method, capture these parameters. I could then, for example, loop of the wages and then also come up with the average. Then that's what you see here. It is possible to actually combine this with other parameters. It is in fact possible to say I'm going to take into wage 1 and then still include the params keyword. What this will do is, it will make sure that monthlyWage 1 one goes in this first parameter, and then all remaining parameters will go into this wages array. Because of that, the parameter with the params keyword needs to be the last one in the method signature. One more thing, unlike out and ref, using params does not require an extra keyword when invoking the CalculateAverageWage method.
Chapter 13 Named Arguments & Optional Parameters
Can we just randomly leave out an argument when calling a method? Well, yes and no. Let me explain. Using optional parameters, it's possible for us when creating a method to specify a default value in the parameter list for one or more parameters. When we then invoke the method, it's possible to leave out the optional ones, and then the method will rely on the default that we have defined. Let me show you a small snippet with optional parameters. In the snippet on the left, I have defined an optional parameter.
Notice that I have added a default value for the third parameter, so the c. That is now an optional parameter. We don't need to add another keyword here or something to make it optional. By just adding a default value, C# knows that it needs to use the default value if we omit it. Here, I'm calling this method.
In the first call, I'm just passing in two values, meaning that the default value for c will be used. In the second line, we call it regularly, so with three arguments. In one go, I also want to explain named arguments. Named arguments have also been around for quite some time. They were added in C# 4. What are they then? Well, normally when invoking a method, the order in which we specify the arguments matches the order in which the parameters are defined in the method declaration. Argument 1 matches parameter 1, argument 2 matches parameter 2, and so on. However, it's possible to specify the name of the parameter when using an argument, and then the order is not important anymore. Matching happens based on named arguments instead. Let me show you this in action. In the snippet on the left we have our regular method again, nothing special, and on the right I'm calling the method.
Notice that I now specify first the name of the parameter, b, then a colon, and then the value for b. And thus first passing in b, I can only do so by stating this explicitly in my method call. I'm doing the same here for a as well. Why would you want to do this then? Can definitely improve the readability of your code should your method have a lot of parameters. Back to Visual Studio, we'll now see these features in action. First, I'm going to show you how to use optional parameters, and then I'll show you how to use named arguments. Say that we have the following: I have again my CalculateYearlyWage method. Now it's called CalculateYearlyWageWithOptional, and it is
receiving, again, the monthlywage, the numberOfMonthsWorked, and bonus.
But say that bonus, well, defaults to 0. Maybe it shouldn't be required for users of this method to always pass that in. Well, that I can achieve with optional parameters. What I can do is add a default value to the bonus parameter. This is now an optional parameter. It has automatically become optional when I'm using this method. So it's now possible for me to invoke this method with two or three parameters. I'm invoking it here, and this still works, but I can also remove this last argument, and it still works. Where we now call the method, the value will actually be defaulting to 0. Let's try it out. Indeed, bonus is 0.
So optional parameters make it possible to omit passing a value for one or more parameters, the optional parameters need to go at the end of the list of parameters in the method signature. Let me in one go also show you named arguments. I've again variation of my original method, CalculateYearlyWage, and now I'm going to invoke it using named arguments. Using named arguments, I don't have to
make a change to the original method. I am going to use the names of the parameters when specifying the arguments for my method invocation.
I'm going to invoke my method, and instead of just passing the values here, I'm also going to declare which parameter I'm specifying the value for. So I can say, for example here, use bonus, and I then specify a colon, and so this bonus points to this bonus, and I pass it the value of bonus, the local variable. I can then do the same, for example, specifying the numberOfMonthsWorked, and, again, use a colon, and I set that to months. And finally, I need to also specify the monthlyWage, colon, and that will be then the monthlyWage.
And we show that value again on the console. So what you see here is that we are invoking the original method, and I can still invoke it as before. But with any method, I can now use named arguments, which is going to allow me to change the order of passing in my parameters. In the very last demonstration of this chapter, I'm going to show you syntax, a syntax to make short methods, well, even shorter. But I can imagine the first time you see this it might be confusing, so I do want to include it here. Let me now show you the syntax, which can be a bit confusing, well, the first time you encounter it.
The method syntax basically can be used for methods where the body consists out of one single line. If the method is a void method, it can just be one operation. If the method does have a return type, that single line must also return the value of that given return type. Let's write it together here. I have a method here that will call the CalculateYearlyWageExpressionBodied. That will be the one that I'll need to write, so let's do that now. So I'm going to create first the method signature. That hasn't changed. But now instead of writing the method body between curly braces, I'm going to introduce a fat arrow. A fat arrow exists out of an equals sign and then a greater than side, and then no curly braces. I can just write my code. In our case, the method needs to return and integer type, so my single expression, my single operation I write here, should also be returning an integer value. So what I'm going to return here is a calculation we've already used quite a few times.
That would be monthlyWage times numberOfMonthsWorked, plus the bonus, and That already is syntax. So as you can see, it's a very concise way of writing methods that have just a single line of code in their body. We've learned a lot of priority methods. We started with understanding why we should create them. They greatly help with improving the code reuse. We then spent quite some time understanding how we can pass parameters, so by value and by ref. By value is the default and creates a copy, whereas by reference works on the original value. This knowledge will come in very handy in the next chapter, and we'll dive deep in reference types in C#.
Chapter 14 Value Types & Reference Types
Types are important in the C# language. Right from the start of this book, we have been looking at the different types that come with the language, and very early I mentioned that types in C# can be split in two large groups, value types and reference types. Welcome to this chapter, Understanding Value Types and Reference Types, where we'll dive a lot deeper in what these are exactly, how they behave, and then we will continue our path by understanding the different options to create custom types. I want to warn you. This is a crucial chapter to understanding types in C# in more depth, so fasten your seat belts. That I have your attention, let's get started. I'm going to explain you briefly what the Common Type System in C# is and how this relates to the different data types in the language. Then we will start working with custom types. First, I'll explain you why we need this, and then I'll show you how we can create them. We will start with enumerations, which are very simple to understand, and we will finish the chapter by looking at the struct. Before we continue our exploration of types, it's time for a little bit of theory about the CTS, a.k.a., the Common Type System in C# and .NET. Although I want to keep the theory part as limited as possible, I need to give you some understanding of this thing called the Common Type System, often also referred to as the CTS. The CTS is in fact a .NET thing, it's not specific to C#, meaning that it applies across all languages in .NET. It is a standard, and it's used to define how types and values of these types all lay that in memory. The CTS describes different
constructs we can use when using a .NET language, including C#. And since multiple .NET languages follow this standard, entities created with one language can also be used from another language. Indeed, when I very soon will start creating a class in C#, this class can be used from an application written with VB.NET, so Visual Basic .NET. This is because the different languages follow the same standard defined in the CTS. What the CTS also does is defining the two categories for all types. A type in C# falls in either of these categories, and we have touched on them already before. Indeed, a type in C# can be a value type or a reference type. Instances of value types will directly contain the value, hence the name. Instances of reference types do not contain a value. They point to the value, they reference it, hence they're called reference types. Whatever the type you're using, it will always fit somewhere in the type hierarchy of .NET. Every type in .NET does so, really. At the very top of the hierarchy, you have the object type, and basically every other type will derive from that. When we look at inheritance later in this book, this will all become a lot clearer. In the object we have many types built into .NET, and thus usable from C# are types such as string, which we looked at already, array, exception type, file types, and many, many more. All of these are types. A value type is itself also a type, and it's the parent of the value types we looked at already in this book, such as double, byte, float, Boolean, and so on. The latter two are types, but we often use them through the keyword, double, pool, and so on. And, yes, keep in mind that everything within .NET and C# is an object. That will come in handy very soon. As said, the CTS, the Common Type System, defines a framework, a standard, that languages can rely on to be able to interact with one another. Part of this standard is the different categories of times that are possible to create and use in C# and .NET. There are five categories of types, enumerations, structs, classes, interfaces, and delegates.
This can be a bit confusing, as we're dealing with a lot of new things here, but bear with me, it will all become clear very soon. That's a promise. The different categories of types fall in one of the two large buckets of types you have looked at, value types and reference types. Enumerations, which I will cover in this chapter, and structs, also covered in this chapter, will be value types. They will indeed contain their value and are created on a stack. The three other categories, classes, interfaces, and delegates, are reference types. Classes are so important, we need two separate chapters to cover them, and we will start doing that in the next chapter. Interfaces are covered near the end of the book. Delegates, they fall outside of the scope for this book. For now, remember that the CTS knows about these different categories of types, and they either behave as a value type or as a reference type. It is not something that you can choose. When you create a struct, it will always be a value type. So far, what I have used in this book are types. We've used integers, bools, strings, and more. Those are all types that come with .NET. Indeed the .NET Framework itself is a huge collection of types. Both predefined value types and predefined reference types are included, quite a lot of them really, literally thousands. Very soon we'll see how we can also create our own types. Before we go that route, I want to be 100% sure you're clear on the behavior of a value type and a reference type.
Remember, all primitive types that we used are value types, so they can be used to learn more about the behavior of value types. If we create a few integer values a + 1, b = 5, what essentially is happening is that these variables are created on the stack, and they immediately contain their value. If we could look in the memory of our C# application, think of them like a box with the label a on it, and when you look in that box, it'll contain the value 1. When working with reference types, we'll see a different behavior. Let's create an object. An object will be a reference type. When doing so, the actual object will be created on the heap. The variable, doesn't contain the actual object, rather it contains a reference to the actual object on the heap. If you have been working with, for example, C++, this will look very similar to a point, and that's exactly what it is. However, C# is what is known as a managed language, meaning that we don't need to clean up our objects manually to free up memory space. More on that later, when we look at the garbage collection. A really good understanding of reference types and value types, you're now ready to start working with custom types. Let us dive in and learn what they are and how we will create them. So far what we have used is mostly variables, int a = 3. That's a first step. But just regular variables won't get us to the moon and back. They are pretty loose. We need a way to represent structures, models that we have in real life. What we will need is to bring together data and functionality on that data to represent a structure, and for that we'll have to create custom types. Creating custom types is a very common thing to do in C#. In fact, almost all of our code will live inside of a class, the most commonly used custom type in C#. Classes are reference types, and they are, as said, very common. All code we've written so far actually lived in a class, the Program class which was called when our program is executed. Next to the class the struct is also another custom type that we'll look at. It's very
similar to a class but has a few specific properties we'll need to discuss. In .NET Core, we have the Base Class Library, or the BCL. The Base Class Library is a large library that we can use when writing C# applications. It contains a huge number of custom types, mostly classes, that we can use from our own application code The BCL offers all kinds of functionality already baked in so that we don't need to write it again, going from working with files, drawing on the screen, looping over items in a list, connecting to a database, and much, much more. As said, why we need custom types in C# is to represent a structure, the brush to draw on the screen, that connection to the database. All of them are custom types which map to a structure. Typically, custom types will contain data and define their own functionality. Data and functionality is what makes up a custom type. .NET comes with many custom types built in that we'll use all the time. At the same time, we'll constantly be creating new custom types that we can also share with other programs if we wish to do so. All types, the ones we define, as well as the ones coming from .NET, will live in an assembly, a way to package, distribute, and use types. We will look at using a custom assembly in the upcoming demonstration. Now since there are so many types in .NET, throwing all of them in one big bucket would be quite a mess. Probably there would be naming collisions too. Multiple types within .NET could share the same name, and, by default, that would not be allowed. That's why classes are grouped in what is known as namespaces, another heavily used concept when working with C#. It's a way to organize types while in groups. You can think of it like a folder with subfolders, and the types live in the folders and subfolders. Because of this organization, we can have multiple types with the same name, as long as they live in a different namespace. Take a look at this hierarchy, since that's really how namespaces are organized.
At the very top we have the System namespace. System itself can contain types, but then there are other namespaces whose name also starts with System, then a dot, and then, for example, Web. In that namespace there will be all kinds of types that have to do with functionality. And the nesting can be deeper, a lot deeper, in fact, as you can see within the System.Data namespace. The types in .NET are placed in namespaces, but also our custom types, we'll see later, will be placed in custom namespaces. Using namespaces, we avoid naming collisions between different types with the same name.
When using a type that lives in a given namespace, we do need to point out to .NET where it should look for the type. We can do this by bringing in a using statement for the type's namespace at the top of the file. By adding a using statement for one of the namespaces we just saw in the previous screenshot, System.Data, all types inside of that namespace now become available for use within our code, such as you see here with the Data table that lives in the System.Data namespace. Visual Studio has a handy tool to look at existing types called the Object Browser. It's easy to see what types exist and what namespaces they live in. I'll show you how we can use the type that lives in the base class library, and I'll show you how we can bring in a library, again, containing custom types. The class library that we get with .NET Core is huge, and it's sometimes difficult to understand really what is in there.
Also, when your application becomes large, it is also not easy to understand which types you have in there. To get an overview of that, we can use the Object Browser. Go to View and then select Object Browser.
The Object Browser will give me an overview of all the namespaces that we have in .NET Core and the classes in there. We'll talk about classes in a lot of detail very soon. Here at the top of the Object Browser, you can select the .NET Framework or the version of .NET Core that you want to browse. As you can see, there's quite a long list of options that we can look at here. When you set this option to My Solution, it will show you the list of namespaces used in your application, as well as custom namespaces that you have brought in. Our application is shown here as well, any namespaces that we have in here, and any classes that we have in here are shown in this view. There are many views that you can use here. You can here in
the Object Browser, and then you can say View Namespaces. What I now see is a list of namespaces, and we can also drill down into the classes within those namespaces. And we click here on System, and we see the classes within the System namespace. And let's take a look at our Console class.
And now we see here on the right the members that we have available on the Console class, for example, the Beep method, the Clear method, and, , also the different overloads for the WriteLine methods. We've used quite frequently already the WriteLine here. I on that, we see at the bottom, the description, the summary of the method, and the description of its perimeters. Optional exceptions that can be thrown by this method will also be listed here. But we also can use search in here. We can, for example, search for collection classes. When I search for collections, I will see here also the namespaces that have the word collections in their name. We'll
often work with generic collections. They live in the System.Collections.Generic namespace. We won't be covering collections in detail in this book. There are other books that go deep into this, but let's not focus on that. Let's expand what we have in here. For example, we have the List in here.
The generic list, the List that is, is a very commonly used type of list, and it lives in the System.Collections.Generic namespace. I can actually click on this link here, and this will take me to the namespace again, and it says here that this is a member of System.Collections, and I click on that. I actually see that this is part of an assembly. An assembly is, let's say, a unit of packaging code in .NET. It's a DLL part of the .NET 5 framework that is automatically referenced by our application. That means that the namespaces, as well as the classes inside of System.Collections are available out of the box. That means that when I go back to my code here, I can use the generic list. Now as you can see, Visual Studio doesn't know about List, although List was, like I said, inside
of the System.Collections.Generic namespace, which is in DLL that is automatically known to our application. It doesn't show up here.
My compiler says I don't know about List. What I can now do is I can click on this lightbulb here and then say bring in a using statement for the System.Collections.Generic namespace. This now makes all the classes within this namespace available within this class here, and I can continue writing this, and this will compile fine. There are numerous namespaces and classes within the .NET Framework, so Microsoft offers you out of the box already a lot that you can use in your C# applications, but, , not everything is in there. That's why we can also use external DLLs, external libraries, and use these from our application.
To bring those in, we have a tool inside Visual Studio called NuGet, which we can access by on the project and then saying, Manage NuGet Packages. NuGet is a package manager inside of Visual Studio, and it allows you to browse for numerous packages available on the NuGet feed. Say that you want to, for example, work with JSON. I can click on this Newtonsoft.Json library, and I can install that in my application. This will download the DLL for me into my application.
I will now expand the dependencies inside of my application here. I can see under Packages that Newtonsoft.Json has actually been referenced. And I go back to the Object Browser, and you'll also see here that Newtonsoft.Json is now listed here because it's part of my solution. I can browse it here from the Object Browser, and I can see which classes are in there. There were many classes in here that you'll use once you start using this library when you want to serialize or deserialize JSON. I'm not going to go that far, but I do want to show you that by adding a NuGet package and, in fact, adding a reference to that, I now have the ability to use these classes inside of my application as well.
Say that I want to use, for example, this JsonConvert class. I can simply now write again here in my application, JsonConvert. By default, Visual Studio will say, well, I don't know about JsonConvert, but again, click on the little lightbulb here, and add a using statement for that. Now I connect to use that here inside of my application.
So now you've seen the Object Browser, which allows you to browse many, many times in both the library, as well as external libraries that have brought in, in this case, through NuGet, and NuGet is the package manager that comes with Visual Studio. It allows you to bring in many functionalities through external libraries for use in your C# applications.
Chapter 15 How to Create Enumerations
We've now seen that .NET contains many custom types, and now we'll start using the different categories of types baked into the CTS, and we'll start with enumerations. So what's that enumeration thing I've already mentioned a few times? Imagine that your application has a numeric value that you want to assign a name to, simply because the numeric value itself makes the code harder to read and understand. A good example would be an employee type. Maybe in your application, you want to distinguish between employees of employee type 1 and employee type 3. But, what are these values? What meaning do they have? One and 3 won't ring a bell. But we can create an enumeration, which then allows us to give a name to these different employee types. From then on, we can use the manager enumeration value to refer to employee type 1. We can create our own enumeration types, as we'll soon see. And internally there are still value types. Indeed, they are in fact nothing more than an integer value with a name, and we create them using the enum keyword. Here's a custom type.
A custom enumeration is the EmployeeType enumeration I was referring to in the previous screenshot. If you want to, in our case, refer to an employee type, we can create an enumeration that contains one or more enumeration members. Creating the enumeration is done using the enum keyword followed by the name of the enumeration, and that's here, EmployeeType. The members are the different values, the different options for the type, and they are strings. Here I have defined Sales, Manager, Research, and StoreManager.
Behind the scenes, C# is actually assigning default values for the int type to the different enumeration members, starting with 0 and increasing with 1. So in our enumeration, Sales actually gets the value 0, Manager gets 1, and so on. You can also define your own value. So perhaps you want to map to numbers that you have in a database or in a file.
By default, enumerations map to integer values, but this we can also change, for example, to a short. And we can also ask for the value that sits behind the named constant. By just asking, the string representation of Employee.Sales will get in 0 in our case. Now we are going to create our first enumeration. Then I'll show you how you can actually use it in a useful way. Finally, I will also show you how we can access the value that sits behind the enumeration value. I'm going to again try calculating the wage for my employees, but within Shop, we have multiple types of employees, and based on that, their wage will probably be different.
Say that we have these employee types maybe sitting in a database, and in the database, they can be represented as EmployeeType 1, an EmployeeType 2, and so on. Working with types 1 and 2 in the code, well it works, but it's not really handy because later on you're not going to remember what type 1 or type 2 was actually referring to. For that, we can actually introduce an enumeration, and that's what I'm going to do here. I'm going to, notice now outside of the class, create an enumeration, and it starts with the keyword enum.
So I'm going to create a custom type of an enumeration, which is a category of types we can create in C# applications. So I'm going to
call it EmployeeType, that's the name of the type, and the actual body of the enumeration again will live between curly braces. And inside of this, I'm going to write the enumeration values. Say that we have an EmployeeType Sales. Well, I write straight Sales, and that EmployeeType is now unknown type. By default, this will be representing the value 0. If I add another one, Manager, for example, well, now I have two possible EmployeeTypes, Sales and Manager, and it's also bringing the type Research, and finally a StoreManager. So what I've now created in my application is really 4 values that have received a name because this will default to the value 0, but I don't have to use the value 0 to represent someone who's in sales, I can actually use the Sales EmployeeType. This represents 1, this represents 2, and this represents 3. Now say that I have these values, for example, living in a database, and I may actually want to have these enumeration values correspond with the actual values in the database. They might not be 0, 1, 2, and 3, I may want to define these values myself. Let's do another enumeration for StoreTypes. As you can see here, now I've defined the values myself. So these could be corresponding to values in the database. So what I now have is custom types, I can now use these in my application as named values. Let's try that out. I'm going to again create a new version of our CalculateWage method, but now I'm going to use our custom types, or enum types, inside of that method. So, let's go ahead and do that here. I'm going to create, inside of the Program class, again a private static void, CalculateWageMethod, but now inside of the parameters, you're going to see something different. Let's take a look. I'm going to pass in the baseWage, and that's going to be an integer, but now next, I'm going to pass in a variable of our EmployeeType. So, the enumeration has now become a custom type, and I can use to create a variable of, and I'm going to call that EmployeeType. In a very similar way, I'm going to use the
StoreType to also create a variable of type StoreType. So what is important here is that just like I've used the types, I can now use my types to create variables of. This is again a regular method, I'm going to put the body between curly braces. In here, I'm going to calculate the wage, and I'm going to create a local variable of type int, and I'm going to set it to 0.
Now in my method, I can also check if the EmployeeType that is being passed in is equal to the ManagerType. So instead of writing here 1, I can just say well check if this actually is a manager. This makes my code much more readable, and also if you need to come back to it later on, it's much easier to know that the value 1 corresponds with the manager. If that's the case, well then I'm going to do the baseWage times 3, and otherwise I do it times 2. I can also do the same thing for the StoreType. If the StoreType equals a FullPieRestaurant, well then I add in another bonus, and finally I will
write the calculatedWage to the console. I still need to use my method, so I'm going to add another method here using enumerations, and while we're at it, notice what I'm going to do now. I'm just going to write the name of the method here, I'm going to go back on that method, and then I'm going to go to the light bulb here, and call generate method. This is going to generate that method, and we can now implement it ourselves. So this saves us from a bit of typing. Now in here, I'm going to create a new variable of type EmployeeType. Because I need to pass it to my method, I'm going to say that this is a manager, so that's again a local variable of type EmployeeType, and I'm going to do the same thing for the StoreType, I'm going to create a variable of that one as well, and I'm going to set it to do Seating. And finally, I also need to define the value for the baseWage, it's going to be 1000. And now I can call my methods just like we've done before. I'm going to call the calculateWage, going to pass in the baseWage, I'm going to pass in the EmployeeType, and that's the one with the lowest case because that's a variable that we're passing in. So I'm now passing in that we want to use a manager EmployeeType, and I'm going to do the same thing for the StoreType. Let's take a look at the result to make sure that we have done everything correctly.
And there we go, the calculatedWage is 3000, that's correct because it was a manager, but it wasn't for a FullPieRestaurant. Enumerations are a first custom type and, frankly, they are very simple, and apart from naming a value, you can't really do a lot more with them. Let's now turn our attention to the struct type in C#. The struct keyword is really already saying what a struct is doing. It's going to allow us to create a custom data structure. So we can use the struct as a
way to describe a model, a structure with data. Structs are typically used for lightweight structures, so with limited amount of data. Behind the scenes, a struct is still a value type. We can instantiate a struct using the new keyword. Basically it means that from the structure we can create a new instance. A struct can contain data but can also contain methods and even other members. If you're familiar with classes, you'll see that they are very similar to classes, but they have less options. While it's technically not correct, you could say that they are lightweight classes. As said, using a struct, we can wrap data to represent a structure.
If you want to represent in our code, say, an employee, what would we need? Well, an employee has a name and works in a given department. Those two pieces of data and probably quite a few more can be grouped together to represent that employee. And that we can do by using a struct, as you can see here. To create a struct, we'll first write the struct keyword, followed by the name we want to use. Here that would be Employee. The body of the struct is again between curly braces, and inside the struct I have defined two fields of type string, Name and Department. I use the term fields here, not variables. What's the difference, you may ask? The field is a variable defined on the struct root level, and the Name will also be used in
working with classes, as we'll see in the next chapter. So now we have defined a struct. The struct is a template to represent employees. We can now create a variable of our new type called Employee. So Employee now becomes a custom type, and I can create a variable of that type. Then I give a value to the fields of my newly created Employee. The Name field and the Department field are both receiving a value here. At this point we have created a new employee and its fields have been initialized. Now a struct is more than just a wrapper to group together some fields of data. Indeed, to a struct I can also add a method. This method adds functionality to the struct and typically in the code inside the method we will work with the data of the struct.
I won't go too far in this yet. We will see more on methods when we look at classes in the next chapter. I want to give a discussion on structs a bit shorter, though. While struct can be used, you'll more often use custom classes, which we'll learn about in the next chapter. In the final demonstration of this chapter we'll learn how we
can create a struct and use it from our application. Up until now, we've always just used loose variables to work with our employees.
That has to change because I want to be able to represent in my C# code an employee as a set, as a type, really, and I can do that using a struct. Again, outside of the main class, let's create a struct. And we can create a struct, as a structure, that is, by using the keyword struct. What I'm going to do is I'm going to represent in code the concept of an employee. So I will call my struct here Employee. Just like before, the entire body goes between curly braces. Do know I can also put this, it a separate file, but just for the sake of simplicity, I'm going to put it here in our original file. We'll come to creating multiple files in the next chapters. Inside of this Employee struct, I want to, as said, represent the employee. What does an employee have? Well, an employee will typically have, I hope, a name, so I'm going to create a variable Name. It is actually
a field. It's a variable on the struct level, so in the root of the struct. I'm going to do the same for the wage. I'm going to create another variable of type int, in this case, and that's going to be the wage. So now I have a number of fields, a number of variables, that I can use to represent a single employee. What will an employee typically do? I hope, at least, they will do some work. So I can also represent functionalities that we typically model on an employee on this struct. So I can now create another method, and now notice I don't have to write the static keyword. I will come to why that is very soon, but notice that within this struct I don't have to do that. I'm going to create a method, Work, and then it's going to, in this case, just write something to the console. Do notice here in the Work method I've used the Name variable, that is, the name of the current employee. So now I can actually, from my application main code, call a method that will create one of these employees. Let's do what we did before. Let's call using a struct. And Visual Studio will again say, I didn't know that, so let's generate it. And now I'm going to create an employee. So what I'm not going to do is I'm going to instantiate one of our employees by calling Employee. It's, again, a time that is now known in C# because I've created it as a struct. I will now create arrival of type employee, and on that employee I can now give it a name. Let's call the employee Bethany, and let's also give that employee a wage. So notice what I've done here.
I've created an employee of type Employee, and then I have access to the variables that I've defined on the struct, and I've given them a value. And I can now also on that employee call the work method, and that will then ask the employee to perform work. So now I have, inside of this struct, captured functionality as well as data that represents an employee, and I've created one, I've given them values, and then also ask it to execute that functionality. That is the output of the Work method. So Bethany's probably baking a delicious pie for us because we've done a good job. A deep understanding of types is essential to understanding C#. As said, everything is a type. We've looked at the CTS, which is a standard to define which type categories can be defined. We've then already looked at two of these custom types, enumerations and structs. While this chapter was crucial to progressing with C#, I think the next one, too, is really
important. We'll learn about creating classes and objects, the most commonly used custom type in C#.
Chapter 16 How to Create First Class & Objects
C# is an language. That means that it's easy to represent structures in the language and let our code represent, well, models and their interactions. Within the previous chapter, we've see the struct, and while the struct can indeed be used to represent these models, they can't be used to do, well, everything that we'll need. That's why classes are used so much more often. Welcome to this chapter where I'll teach you how to create your first classes and objects. You'll be doing this all the time while writing your C# code. So first, I want to give you a bit of background on classes. Then, throughout the rest of the chapter, we'll be creating the Employee class. Creating it is one thing. We'll also need to use it, and that's what we'll do next. In the last part of this chapter, I'll explain to you about properties on our class. As said, before we start using classes, we need to understand them, so let's do that first. So what is a class? Just like a struct, a class can be used to represent a concept or model. Think of a car, an employee, or a dog. To represent these in code, you will almost always rely on classes. A class is really the blueprint of an object. Keep that sentence in mind when you're not familiar with the concept of programming. A class is a blueprint of an object. What does it mean? We will create classes that represent a concept, and we will instantiate an object from that class, which represents a single instance. Employee will be a class, but Bob and George, they're our objects, they are real employee instances. A class contains the data to represent the structure, and it also defines methods which expose the functionality of the class. Creating classes
is done through the class keyword. As you may have guessed, we'll soon see how it's used. And yes, classes really are the building blocks of just about anything in C#. Classes are the foundation of programming, also known as OO. OO is about creating classes, and it's what we will typically do in C#. I will refer to this a few times later on. All code we will typically write, or maybe I should say nearly all code to be correct, will live inside of a class. Indeed, in C#, you can't put a method just anywhere in your code. It has to be inside of a class. The same goes for a variable. I can't just place it randomly where I want. It needs to be inside of a class. And you may be thinking, did I not write variables outside of a class up until now? And it may be so that you hadn't noticed that all the code we wrote was inside of the main method inside of the Program class. All of our code so far was in that one single class. Try putting a method outside of a class, you'll see Visual Studio complain pretty quickly. The compiler will notice you're doing something that's not allowed. Classes are reference types. When we create objects, very soon, you'll learn that these objects are created on the heap, so not on the stack. Classes are made for the heavier stuff compared to structs, and yes, most things I have just told you would also work if you use a struct. Structs do contain methods and variables. But remember, the structs are value types, and they are not that commonly used when building C# applications. Most of the time, when you're doing just anything, you'll be using a class. Okay, what does a class look like then? Let me show you its structure.
First, we have the class definition using the class keyword followed by the name of the class. Here, that would be MyClass. I've prefixed MyClass with an access modifier, here public, making the class accessible from pretty much everywhere. Then, again, between a pair of curly braces, I have the class body. In there, we will have the code for our class. They also creates the scope. When creating a field, a or b, here inside of the class, that field is available in the body of the class. Inside of the body of a class, we can write our code. We can use fields. I have introduced fields in the last chapter. They are basically variables that will hold the data. We'll also include methods, which will contain the functionality and work on the class data. Later in this chapter, we'll learn that our data is typically accessible through properties, which will wrap the field data, but more on that later. Also, classes can contain events. Events are used to enable classes to notify other classes about something interesting that happened, such as a button click. I'm sure you now already understand what classes are about. Let's start going practical now and create one. I'm going to take you through the steps of creating
our first class, the employee class. In the context of Shop HRM, our application will work with employees. If you think about an employee in real life, they will have a number of things that define them. An employee will have an identity, the name, being the typical thing here. An employee will also have other attributes that define them, their age, their wage, and probably dozens more. Also, an employee will do things; they will hopefully do work and they will get paid accordingly. I'm sure you can come up with many other behaviors as well as attributes for an employee, but let's keep it simple for now. So let us apply what we have learned and create a class to represent that employee.
The logical choice would be creating a class using the class keyword, and I would think employee would be a good choice for the class name. On the class, we'll need to store its data. For this, we'll use fields, and they are just variables on the class level. In there, we can store values. For now, we'll use just fields to store data, but very soon, you'll start seeing me using properties, and once we do that, we'll also discuss how these fields will end up being private, but more on that soon. Let's first add a few fields. Here, you see, I've now brought in fields that will contain the attributes for the employee. I've used camelCase here as a naming convention, meaning that we start the name of the field with a lowercase and we capitalize then each subsequent word. That is pretty much the
convention used in C#. Notice that also for now I've added these fields using a public access modifier.
That means that these fields will be accessible once we start creating objects. Classes can contain more than just data. To add functionality to a class, we'll bring in methods again, which we have already covered in an earlier chapter in this book. Methods are used to perform actions, and they typically will do this on the data stored in the fields. The result is often a change in the state, meaning a change in the value of one or more fields. So what can an employee do? Well, perform work as we've said before. That's a functionality we'll want to pair to our class so that will be a method. For now, this method is not returning anything, so it's return type is set to void. Notice now also that we don't need to add the static keyword like we've done with the methods we've created before.
I will come back to why that is when we know a little bit more about classes and objects. Our methods, too, received the public access modifier. Public will make this method usable from the outside. It means that when we will be using our class, this method can be invoked on objects of this class. It's part of the public API of the class. Think back of the other analogy I've used a couple of times already, a car. A car will expose to you the functionality to drive. It's a public functionality. Private means that the method or field is only accessible from within the class. Never will you be able to interact with it from the outside directly. It's typically used for methods you don't want anyone to use directly. It's the inner workings of the class. In a car, this would be something internal to the car. The car itself knows how to inject fuel into the engine so it's functionality, but it's not typically something you'll call onto while
driving. Driving would become very hard if so. Finally, we have protected. That means that the functionality is available for the class and its inheritance. We'll learn more about inheritance later in the book, and we will revisit this protected access modifier then. I'll go and create the class, bring in fields, and add methods. So I'm going to, for the sake of this demonstration, create a new project.
Click on Next, and then in the Target Framework I will choose .NET 5, but other versions will work fine as well. So we have the class Program, we've had that so far, and we've used that so far, but now I'm going to start creating my own new class, and I'm going to do that like I typically do that in a new file.
So I'm going to here on the project, and I'm going to select New Item. And in here I'm going to select under Code new Class. And typically this is not required, but typically I will actually give the same name to the class and to the file name. It makes it easy to find things in large replications. So I'm going to create here the Employee class, since that is the class that we'll be creating. So click on Add, and then Visual Studio will create a new class. It's going to
be a very simple template that it executes. That simply creates a new class for me. And we can see, indeed, that Visual Studio has executed this template, which creates a new class, a very simple class. It just has the class keyword and the Employee, so that will be the class name. And the actual code, the body of our class, will go between these curly braces again. I will actually make this into a public class. This means that this class is accessible from within this application, but also should I use this code in another application, should I reference it, this class will also be available. By making it public, it's available within and outside the assembly. Now talking about assemblies, as a quick side note, if you here on the Properties, you actually see here the Assembly name. That's actually when our application is going to be compiled, it's going to become an executable application, and the name of the assembly of the executable file is going to be. It is by default also the default namespace that Visual Studio will give. Indeed, as you can see here, the namespace that this class is in, but we'll talk about namespaces later on in more detail. Now if you think about an employee, what data about an employee shall we store? Well, we've already done it with the struct in the previous chapter, but now we'll do it in a bit more detail. So I'll bring in a firstName, a lastName, and an email first. As you can see, I've now defined on my class three public variables, and again, these are called fields because they are variables on the class level. I'm using CamelCase. That's the typical way of naming class variables or class fields. These variables will be used to store data about the employees.
I'm going to bring in a few more fields. I also think that when working with an employee, we're going to need the number of hours worked, the wage, and the hourly rate for an employee, so I'm going to create fields for those as well. I'm going to bring in also a DateTime field. The DateTime is going to be the birthday of the employee I also want to store. I may want to send a gift to my employee, so I definitely need to store that piece of data of the employee as well. So I simply add it here as another field. Remember in the previous chapter we created the enumeration EmployeeType. This was a way to store information about the type of an employee and it created that custom time for that. What I can also do is create another variable here, another field of the EmployeeType, and let's call that employeeType. This will be a variable that uses that other custom type, that enumeration that we created, which is then going to be used to store information about the employee type for a given employee. One cool thing is that you can ask Visual Studio to move this to a separate file. I tend to put separate types in separate files. So there we go, now this is moved to a separate file, but we don't need to make any change. The EmployeeType is still found.
At this point, the class contains just fields, it just contains data, and it can be used to actually create a container of data. But very often we'll also bring in functionality on a class that will then work with this data, and so that's what I will do here as well. I'm going to create a number of methods to wrap this functionality. I'm going to, for example, create a method, PerformWork, which is something that an employee will typically do. I hope at least. So I'm going to create a method and I'm also going to make it public, and I'm going to call it PerformWork. Just like we did before, we suffix the name of the method with brackets.
When an employee works, when they perform work, we will increase the number of hours worked with 1, and I'm also going to write to console that the employee is actually performing work. I should maybe notice that I'm not using the static keyword here. Again, we'll come to what static means very soon. Let me add some other functionalities on my class. I have now brought in a couple of other functionalities that my employee will need to do. We'll definitely need a way to stop an employee from working. I also want my employees to receive a wage, so we'll give them a functionality to do so. That's what you see here. That will take a look at the number of hours worked, do that times the hourly rate, and calculate the wage. It will also set the number of hours worked for this employee back to 0, and it will then return the wage. Finally, I also have a method called DisplayEmployeeDetails, which will write to the console, the first name, last name, email, birthdate, and employee type. So with this ready, we have created our employee class. It now contains data and
it contains functionalities. This is our first real class that we've created.
Chapter 17 How to Use Class
We are now the proud owners of a class, a real custom type in C#. Nice, but we aren't doing anything with it yet. This chapter was called Creating Your First Classes and Objects, so now is the time to start working with a class and start creating objects. A class is a blueprint of a concept of a structure. It's like the plan of a house created by an architect. On the class we have defined perhaps a field called Color, but that is on the class. Now we want to create off of that blueprint of that class, if you will, an instance. That instance is an object, and it can give a specific value to the color, like orange. We can, based on a class, create multiple objects so the second instance or object would have perhaps the value blue set for the Color. It's physically a different object, they are different spaces in memory, and we can continue doing that, for example, setting the color to green for a third object. It's important to see the difference between classes and objects.
Classes are blueprints, objects are the real instances, and they will give a different value to the fields defined in the class. Creating objects is a very common task when writing C# applications. Objects are often referred to as instances, and you'll hear me use the words
objects and instances interchangeably. Saying that we are creating an object is the same as saying we're creating an instance, and so that process is also often referred to as instantiation. So how do we create an instance, or object? Since it is such a common thing to do, let's break down this process in a few steps so you are fully clear on this. We will work with the new keyword, so as you can see here, and we're saying to the new keyword which class we want to create an object from, or in other words, which class I want to instantiate here. We are here newing up an employee, since that's the type that we are creating. In other words, saying new employee will create a new object of type Employee. Notice the brackets here at the end. Between the brackets, for now, I don't have any values defined like we did with methods, but as you'll see soon, we'll often do that. So the path to the right of the assignment operator has now created the object. Let's now focus on the path to the left of it. Well, that will do what we have already done a few times, it will create the variable called employee, so the lowercase, and the type of that variable will be Employee or a newly created class. And what does this variable contain then? With primitive types like integers, the variable contained the value itself. Classes are reference types, so the variable contains a reference to the object, a pointer if you will. Once we have created an object, we can invoke members on it, which I will show you in a minute. And what we're actually using is the constructor. It's a special type of method that is used to create an actual object. When we new up an instance, it'll be done through the use of a constructor. I'll show you how we use them in a minute. C# will always use a constructor to generate a new object, and for that it can use what is known as the default constructor. This means that even if there is no constructor defined, it will generate one. Very often, though, we will create our own, as I'll show you in just a minute as well. When we created our new employee, we had to write
more code to also set the value for the first time. Frequently, the constructor will be used to set initial values on a new instance. Let's take a look at them in more depth. Here's our Employee class again, and now, as you can see, it looks as if I've added another method. But take a closer look. It doesn't have a return type, and there's something else which is special. It's name is the same as the class name. Indeed, these are two characteristics for a constructor, no return type and the name is identical to the class name.
It can accept parameters, but that is not required, and it'll typically also have an access modifier specified. So the highlighted code here is the constructor, and it's called to create the object and set some of its initial values. In the constructor body, you can see indeed that I'm using the constructor parameter values to set the initial value for
my field's firstName and age. So, how do we then call that constructor when I said the constructor is invoked when an object is created using the new keyword? Newing up an instance will always use a constructor. You don't call the constructor explicitly by a name or something like you do with a method. Based on the list of arguments we pass, a correct constructor will be invoked. Indeed, it's possible to overload the constructor as well. Do you remember what overloading was? We learned about creating different methods with the same name, but a different number of parameters or different parameter types. I hope you remember. That was method overloading. Well, that we can do with constructors as well. We can create multiple constructors with different parameter combinations. Here you can see that I'm using the new keyword to generate a new object of the Employee type again. Now to that constructor I'm passing two arguments, the values for the firstName and the age. Just to be sure, here's what the constructor in this case will be responsible for. From my Employee class that contains the constructor, I'm actually creating new objects, and those are created using the constructor when I call new on that. Different objects are being created, so an object with the name Bethany, another one with the name George, and another one for the name Mohammed. Different objects, so different instances in memory, spun off of the same class that offered this new constructor. But wait, initially we didn't have that constructor, and still we were able to create a new employee? I'm glad you noticed.
Do we actually always need to include a constructor before an object can be created of a given type? Well, yes and no. Let me explain. There will, in fact, always be a constructor, but we don't have to create it ourselves. If we omit constructors altogether for our type, C# will generate what is known as a default constructor, just basically the code you see here, containing, well, pretty much nothing. It's a parameterless constructor that doesn't do anything, but it's actually the thing that is called when C# news up a new object of this type. And we can also include it ourselves, and even include some code in the body here. And to be clear, you won't see this constructor appear in your code. This happens behind the scenes. Do note that C# will generate the default constructor only if you have no other constructors defined. Once you include just any constructor, so with or without parameters, C# won't generate a default constructor anymore. So it's not always the case that an empty parameterless constructor will be generated for you. That only happens if you have no other constructor in your class.
On objects, we can now invoke methods defined on the type, as well as access its fields. Here we have our employee instance again, and now I want to have our employee do something. I have defined methods for that on the type, so on the class, such as PerformWork. Using the dot operator, so by putting a dot behind the variable, we can now access the members. We have done this before already a few times, even for very early on in this book when we used, for example, int.MaxValue. I hope you remember.
That was doing the same thing. Using the dot notation, we get access to the methods and the fields defined on the class. Methods are used to perform an action such as here, PerformWork. And I can also change the state of an object, and that's what you see here, where I'm setting the firstName value to Bethany. Again, I'm doing that using the dot operator. In the last line here, I'm also invoking a method, ReceiveWage, and that will return a value which I can then capture in the wage variable. Methods such as ReceiveWage, could also receive arguments when they have defined in the method declaration one or more parameters. All the topics we covered before around methods fully apply here as well, and we'll see that throughout the demonstrations in this chapter. Note that it's possible that depending on the access modifier used in the class definition,
so whether something is public, private or protected, some members won't be accessible to be used. Only public ones will be available to use from the outside. We've learned a lot about objects, so it's time to head back to Visual Studio. First we'll create a constructor, and then we'll start instantiating objects. We'll then use the dot operator to invoke members on these objects. It's great that we now have a magnificent class, but we aren't using it yet. So, we'll create an object to start using that class. Before I can create, before I can construct, I should say, an object, we'll need a constructor. In fact, there is one over here, can you see it?
Well, I can't because you don't see it, but there is a default constructor always available for you. In fact, it looks like this. I can actually create this constructor, and that is in fact the default constructor that C# will create if we do not create our own constructor. So this constructor is always there, so that even if our class doesn't contain a constructor, we can still instantiate it, but we'll do that in just a minute. I will start by adding a full constructor that we'll use to initialize our data. So what does a constructor in general look like? Well, it does need to have the same name as the class name, so where we would typically write the method name, we now write the class name again. That is required to become a constructor, let's say. Then there is no return type, as we can see here, and constructors can also contain an access modifier. Typically, like I said when you create a custom constructor, you'll bring in a couple of parameters so that you can use them to initialize your object. So I'm going to bring in a number of parameters here, let me do that first. So now my constructor contains a first, a last, and an em string, a DateTime for the birthday, an employee type, and a rate double. I'm going to pass these values to my local fields.
So that means that the value that is being passed to the constructor, so in the first parameter, it's going to be used as a value for the firstName field, and I'm going to do the same thing for the other fields and their corresponding parameter. I'm going to move my constructor a little bit lower. I tend to put my fields before my constructor, but that is just the way I work, but you see a lot of
people doing that. So, let's follow that convention. It is also possible to create multiple constructors. I can bring in a second constructor, let me do that first. So now I have created a second constructor, and notice that this one is, let's say, missing the rate parameter. I'm actually creating what is known as an overloaded constructor. We saw method overloading before. If you think back of method overloading, what was it again? Well, it was multiple methods with the same name, but different parameter lists, so it could be different types. or different number of parameters, and so on. Now, with constructors, I can do the same thing, and I have 2 versions of the constructor, one with 1, 2, 3, 4, 5, 6 parameters and one with 5 parameters. Now what I'm actually doing here is something that I haven't mentioned in the screenshots, I'm also going to basically call from this constructor the other constructor, and I'm using for that the this keyword.
The this keyword here is going to tell C# that it needs to invoke the other constructor, the one with 6 parameters, that is, and it's going to pass in for rate the value 0. So at this point, my class is in an even better shape. I now have a constructor, now we have the blueprint ready. So now we can actually start creating objects of this blueprint. We can create objects. That I'm going to do from the Program class. Indeed, we can now, from the Program class, create an object of type employee. That's what I'm going to do here, so let's replace this again with Console.ReadLine so that the console doesn't close automatically, and now finally, we're ready for the big
moment, let's create a new employee. I'm going to use my Employee type, and I'm going to create Bethany, that's going to be the first employee I'm going to create. Quite normal for Shop. Anyway, I'm going to then create that, and now I'm going to use the new keyword. The new keyword is a signal for C# to say, well, I'm going to have to instantiate a new object of the type Employee, and this is then going to, behind the scenes, call the constructor. We have a couple of constructors, we have 2 constructors that we have created, as you can see here. Visual Studio is saying you have 2 offloaded constructors, one with the 5 parameters and one with 6 parameters. I'm going to use the one with 6 parameters here to actually create my first employee. So I've now passed in Bethany as a first name, Smith as the last name, the email, the birth date, the EmployeeType is Manager, and the rate is 25, and let's not forget the semicolon.
So now this is really an object that I have created. I talked about that default constructor before, is that still there? Well, let's see. I can, for example, now try doing the following, but now Visual Studio will start complaining. It says, well, I cannot find an overload that takes 0 arguments. Indeed, if we look back, we created 2 constructors, one with 5 and one with 6 parameters. The default
constructor is gone, so the hidden one, the invisible one that I showed you before, it's gone. Well, as soon as you create just any other constructor, that default constructor is not there anymore, it's unavailable anymore.
So, once we create one, we need to use our own one, and we can indeed bring that constructor back in, so the one without parameters, that's up to you to decide. And let's remove this again. We have that object, let us do some things with that employee. So I'm going to use my object. On that object, I can now invoke functionality, so functionality defined on the class, on the blueprint, that is. I can access that using the dot operator. So I can now call the DisplayEmployeeDetails method using the dot behind the object. This is a void method, so I can just invoke it, it's a fire and and this will do things on our object. I'm going to also ask my employee to perform some work. I'm going to call PerformWork, and I can do that multiple times. And then to thank Bethany, we're also going to pay her a wage. So I'm going to, on the employee, call the ReceiveWage method. So now we've created an object, and once we have that object, we can start doing things with it. So I've added here a few lines of text that we'll see on the console. So now we're
ready to run our application, create an employee, and then perform a couple of tasks on it. So there we go, the employee was created.
This is the DisplayEmployeeDetails, and then we see that we have called a few times the PerformWork, and then we see that the employee has received the wage. So I hope you now really understand the link between an object and the class. The class is really the blueprint, it defines what can be done, but it doesn't really define the values. When I create an object, I will actually give values to those fields, and once I have the object, the employee, I can also invoke functionalities, method studies that are defined on the class onto that object. So far, we have worked with a single object. Let's now work with several ones in the next demonstration. Once we have the class, the blueprint ready, we can create as many objects as we want. So why just create one employee? We can create another employee. Let's create George. So here we have another employee. I call the object george, and it's also using the constructor to set initial values for its fields. And now we have the object, so now I can, on George, also ask to display the details. Also have George perform some work and then also pay George for the executed work
by calling the ReceiveWage method. So here we see that we are creating Bethany that we did before, and then I'm creating George, and now George is performing work and he's being paid for that as well. To invoke a method on the object, I use the name of the object, then a dot, and then I type the name of the method I want to invoke. Optionally, I can also pass it parameters, but we don't have any of those yet.
So far, we've used the methods to let our employee perform tasks, but it's also possible to access the data, the fields from here. And I can own my object on Bethany, for example, change the first name and simply say that object bethany now has as first name the value John. And I can, in one go, also change the hourly rate. And I can set that to 10 instead of 25. If I now run the application again, you'll see that our object, bethany's first name, is no longer Bethany. It's John. And because of the decrease in hourly rate, I said it from 25 to 10, Bethany, or John I should say, is now also getting less money. So to summarize what we've seen here is that, , we can create multiple objects, and we can not only invoke the methods, but we can also change the data, the values of the fields of our object.
Demonstration: Classes Are Reference Types
If I ask you, are classes reference types or value types, what would you say? I do hope you're thinking reference types. Otherwise, I haven't done a good job explaining you so far. Here's how you can easily see that they are reference types and the actual objects are stored on the heap, so the mass memory. I'm first creating a new instance, then I'm setting the firstName. Employee1 is a variable pointing to the actual object created on the heap. I then create a second variable, employee2, and I set it equal to employee1.
At this point, we have two variables pointing to the same object, and that I can prove. If I now, through the employee2 variable, change the firstName of our object and I set it to George, and then I use employee1.firstName to get back the value of our objects firstName field through the employee1 variable, we'll get back George. Why is that? Think about this for a minute. What we have done is we have one object on the heap, and you have two variables pointing to the same object, and if this changes a field's value, it's changed on the actual object. Classes are indeed stored on the heap.
Chapter 18 How to Add Properties
We're now going to extend our class properties, and by doing so, we are going to apply, well, a better design. Let's see what they are really. At this point, our class looks a bit like this. I have my fields, and they're public.
The data of our class is stored in public fields. Now, because these fields are public, we can, once we have created an object, change the values of the fields as we have done before from pretty much everywhere. So definitely from outside the class, as we can see here. I have an object of type Employee, somewhere in my application, and I'm setting the firstName. We've done this before. But while this, , works, it's not a great design choice. Having publicly accessible
fields on our class means that everyone can change its data from everywhere. It's like you're sitting in your car going 70 mph on the highway and then changing the amount of fuel injected into the engine. I'm not an expert on cars, but my gut feeling says that this might not be a good idea to do. Your car should at least check this value before it's allowing it to be changed.
For that reason, we'll typically use properties. Properties are members of a class, just like methods that sit around the private data of a class. They form what is known as accessor. They typically expose the data that we had before, but it can include logic to perhaps validate
if the passed value is valid. Both for getting access to the actual value, as well as for setting the value, they can include logic. Through properties, a class can expose data, but include logic that is hidden and internally works to validate or change the actual value. They can have a special syntax, as you can see here. I have my FirstName field, and that's now become private. Then I have in PascalCase case my firstName property. Its type is set to string, and it contains a get and a set accessor. In the get, we will typically include logic to return the actual data. So here, the private firstName. In the set, we can include logic to set the value, and we get access to the value through another new keyword, value. Here we can include logic to validate the value, for example. Properties are very common, and are, in fact, used much more frequently than public data fields. They wrap fields and give us the ability to still expose data we want to expose, and while doing so, the consumer of our class won't know what the logic is that sits around our fields. This implementation is hidden.
By default, properties will contain a get and set, but it's not required to have both of them all the time. Typically, we will have access to public properties and not fields. They are typically private as set. Using the dot operator, we can now also work with properties. First, I'm setting a value here. I'm using now the FirstName, so with a capital, so that's the property. Simply setting the property to a value will invoke on that object the setter part of the property. If you want to access the value from the outside, we need to use the getter. Again, we don't explicitly invoke the getter. We call on our object .FirstName, and that will internally be wired to call the da logic. Let us now extend our class with properties, and we'll update the existing logic to now use properties instead of the fields. Let's look again at the Employee class, and there is something bothering me at this point.
Take a look at the data here, so the firstName, the lastName, so my fields of my class. They state that they are public, meaning that from everywhere I can change the values. So that means that when I go to the program, I can say it on bethany, well, your wage, dear
Bethany, well, it's now So the problem is that although we have passed data in through the constructor, we can still change those fields. So all the fields like rate, wage, firstName, we can still change them. It is definitely not such a good idea because from everywhere my objects can have their data changed. The data is publicly changeable. Nothing is protecting the data of our objects. We should actually do better. For that, I'm going to introduce properties here. So let's go back to our employee. And instead of having my data publicly available, I'm going to wrap my data in a property. So for all the fields here, I'm going to introduce a property. Let's create the first property together. So let's say that we want to wrap the firstName.
We don't want to expose that data publicly. I'm going to create a property. Typically, the property is going to be public because that is the thing that is going to be used to change the value and get the
value of the firstName field. So it's going to be public. It will be the same type as the field it's wrapping, so it's going to be also string. And then, now I use PascalCase, I'm going to use FirstName. It's a convention, it doesn't have to be the same, but when I create a property for the FirstName field, I'm going to typically also name it the same, but now in PascalCase. Then, I don't need to include records. It cannot accept parameters. It just opens and closes curly braces. This is how C# knows that this is actually property, and it's showing here again and read quickly. That's because I need to introduce a get and a set. The get will contain code to return the value of the field it's wrapping, so that means that in here, I'm going to return the firstName, that is my field, my actual data. In the setter, I'm going to get in the value, and I can optionally also include code to verify that the data is valid. For now, I'm simply going to set the firstName = value. And value is another keyword that is used in combination with properties. It will contain the value that is being passed to this property. You may be confused a bit at this point because I now still have a public property and a public field. I can still access this firstName field, but what I'm going to do is I'm going to make this into a private string. The difference is that from the outside I cannot ask bethany.firstName, lowercase, anymore because that field is now private. This is typically what you'll do. The class will protect its data. Data should be private. We can, however, still change the data, but it will go through that property, and that property then can contain code in the setter to verify that the data coming in is actually valid. Note that you will typically add properties only for data that should be usable from the outside. If you have fields that are purely internal to our class, it's not required to create properties for these. As an exercise, try doing this with all the other fields. I'll come back with an updated example. Compare your solution with mine. I've changed all the data fields to be private, and
then I have created my different properties. Notice that for the wage, I have added a bit of logic because I think it's very unfair to give someone a negative wage. So I'm going to check that if the value being passed in for the wage is less than 0, I'm simply going to still set it to 0. Otherwise, let that value go through. This is the typical validation logic that you can put in your properties that I was mentioning before. And I've done the same for HourlyRate. Notice that our properties are public. In the rest of the code, I'm still using the private fields instead of the properties. Well, it's actually best practice to once you introduce properties to also in your class use these properties. So that means that instead of using firstName here, I'm going to use the property FirstName. I'm going to do the same for LastName. I'm going to do the same for Email, BirthDay, EmployeeType, and HourlyRate. You may be asking, why am I doing that? Well, the wage would be a good example. If I always use the property instead of the private field, I will always have this validation logic kicking in when the value is being set.
So I actually recommend doing that throughout the entire class. Let me fast forward and show you the result. So here's my example. PerformWork now uses the NumberOfHoursWorked, the property, and it's also using FirstName and LastName in the Console.WriteLine, and the same goes for the other methods. I'm always using the properties now. As already mentioned, if the data you're working with is purely internal to the class, we don't create a property that will work with an internal private field. Later in this part, decisional part,
you will learn more about it when you learn the ins and outs of encapsulation. In my class, I can, as I showed you before, now access the properties. I can now, for example, change the HourlyRate, and let's give her an increase. Let's put it to 50, and let's also change the NumberOfHoursWorked, and we'll set it to 100.
Let's put in a bonus, let's say. So you see that I'm accessing the data through the properties. And should I actually set the value of Wage to it will still be 0 because of the logic wrapped inside of the setter of this property kicking in. In the last demonstration of this chapter, we are going to look at a working application where we work with employee objects. For the purpose of this final demonstration of this chapter, I've created a fully working application where we can register employees, register work hours, and pay the employees. I've given it a small user interface. It's, again, a console application.
So now using the console, we can call different functions in the application. I can, for example, start by registering an employee. I need to enter the first name, the last name, and the hourly rate. The employee has been created, and we see, again, the Start menu. I'll do the same for a second employee. And the second employee was also created. I get the option to register work hours for an employee. The application now shows me the list of employees currently registered in the application, and I can select the first employee. I can say that this user has worked 20 hours. I can do the same for Bethany. So I again select option 2, I select employee 2, and I have specified that this employee has worked 15 hours. Finally, I can now on the employees also ask to receive their wage. I again select an employee, and I see that this user has worked now for 20 hours and receives a wage of 500. The hours are also reset. And I select the option 9, the application will quit. So how did I build this? , the application, again, uses the Employee class. And for the sake of simplicity, I've left out a few of the fields and properties that are not relevant in this case. So again, we have the Employee class. It has a couple of private fields, firstName, lastName, numberOfHoursWorked, wage, and hourlyRate.
I then have a couple of properties again. This is pretty similar to what we already had. Then I also have a constructor that accepts a first name, a last name, and a rate, and then I set the local properties to these values. That is very similar to what we've already seen. I then again left out a couple of methods, and just have the PerformWork and ReceiveWage method in here. But I've changed them a little bit. The PerformWork accepts a number of hours worked as a parameter, and it will also return the total number of hours. We could, , also access that value to the public property, NumberOfHoursWorked. I then also created a variation of the ReceiveWage method, which also has a parameter, but it's an out parameter. We saw those before. The out parameter will be returned to the caller as well. In the ReceiveWage method, I then calculate the
wage, just like we did before, I reset the number of hours worked, and then I also assign the value to the out parameter. And I also return the wage. So here you see that using out, I can return two values to my caller. These Employees are used on the Program class. In the Program class, I store my employees in a list. And we haven't looked at lists in detail. Another book further in this path will look at generic lists in a lot of detail. Simply for now, think of this as a type of array in which we can store an unlimited amount of employees. This is an collection of employees that we are going to use here. In the Main, I'll then show some output, and then I've created a do while that will show me that menu that you saw. Based on the user selection, I then will call a different method, and that's what you see here. In a real life situation, we should also validate that the input is valid. That I've skipped here for simplicity's sake. Let's take a look at your RegisterEmployee method. In the RegisterEmployee method, I do some output to capture the data. That's what you see here. The most relevant part for what we are looking at now is this part. Here I'm creating a new employee. I'm calling a constructor. And then I add that employee to the list of employees. That was that local list of employees. So when we call this method the first time, a new employee will be added, and when we call it a second time, a new Employee object will be created, and stored in that list as well. When a use wanted to register work, you saw that the application showed a list of all employees currently registered in that list. I'm actually doing that here using a for loop. I am looping over all the employees and I'm showing the FirstName and LastName using string interpolation. The user can then make a selection out of that list. And again, for simplicity's sake, we assume that the user is going to be making a valid selection. Then the user can enter the number of hours worked. And then we see something that we haven't looked at, really. I then create a variable,
selectedEmployee, and I point that variable to one of the employees already in the list.
And it's based on the selection made by the user. On that selectedEmployee, we then call the PerformWork, and that will point to an object that is actually our selectedEmployee object, and we call the PerformWork method on that. That now accepted the hours worked, and therefore, I'll pass here that number of hours as a variable. This variation is also returning the number of hours worked, so I can capture that here in the local variable, numberOfHoursWorked. That is a local variable here, and then used' in that output. The PayEmployee method is pretty similar. This part is, well, pretty much the same. The only thing that is really different is the out parameter that you see here. I don't initialize the hours worked, but I do pass it in as an out parameter. When I call the ReceiveWage method on the selectedEmployee, I then get back that value as a double, but I also get a value for hours worked, and it is then used here in the output. You now should have a good understanding of classes and objects, and so now would be a good time to build something on your own. I suggest you do an exercise at this point. Given the application we just looked at, try extending it. First, extend the application so that it's possible to change the hourly rate we passed in initially using the constructor for a selected employee. Secondly, add the option to assign a bonus for an
employee, and change the ReceiveWage method so that it now also includes this bonus as part of the wage calculation. Classes are really the cornerstone of C#. Within this chapter, created a few classes, and you've added fields to store data, properties around these fields, as well as methods. Classes are the blueprints for creating objects. When creating objects, we saw that constructors were used.
Chapter 19 How to Group Classes in Namespaces
At this point in the book, my bold statement back in the beginning saying that types are super important in C# hopefully has been proven. Nearly all code that we write in C# will live inside a class or another custom type. And yes, given the amount of attention we've given to classes, it's clear that they are the most important custom type in C#. The .NET Core framework, the base class library itself is built mostly out of types, and we will, when writing C#, be creating custom classes all the time. In this chapter, we will build on what we have learned in the previous one and learn more about custom types. What will we be looking at in this chapter? Well, first, we are going to learn about creating namespaces for the classes we have in our application. We've already looked at using namespaces, but now I will show you how we can create our own. Next, I'll explain that keyword static that we've been using quite a lot already, but I have been avoiding going into a lot of detail about it yet. I'll explain what this one does. It's easy to understand now that you have a clear view on classes and objects. Then, we will discuss the null value. Again, an important aspect when working with custom types. Finally, and this is also related to null, we'll learn about garbage collection. It's a magical feature of C#, and I'll explain all about it near the end of this chapter. Time to look at namespaces again, but now from a different angle. We're going to see how we can group our own classes and other types in namespaces. Remember what we learned a
little while ago? The .NET Framework consists mainly out of custom classes, and, to avoid naming collisions, these custom classes are organized in a structure that resembles a folder structure. Since it's a virtual organization, real folders aren't required, but it's an easy analogy to think about them. So, namespaces are used to allow us to create multiple classes with the same name. Without namespaces, every types or every class should have a unique name for C# to uniquely identify the class. Now, using namespaces, only within a namespace, the type name needs to be unique. .NET itself uses them all the time. We've already seen that. But we as C# developers can and should, in fact, also organize our classes in custom namespaces. We can easily create the namespace. It's just a string, really. And once created, a class belongs to that namespace. From then on, the fully qualified name, so the namespace and the class name, identify the class, and that identification should be unique. Very often you'll use part of your organization name as part of the namespace, since that should be unique. And when I want to use a class that's part of a certain namespace, we can access that by making that namespace available in our code. And for that, I will be adding a using statement in our code, and these using statements, they go at the top of our code. Here you can see that I've created a custom namespace. This is it, really. We just use the namespace keyword and then we define the name of the namespace, which is, like I said, just a string. Typically, you'll use the dot to indicate a Visual Studio will typically create what is known as a root namespace, which is by default a replication name. Then, here I've added HR, which is a custom namespace, and in there, I've placed all my classes that are related to the HR part of the application. The Employee class is now entirely wrapped within that namespace. Let's head over to Visual Studio for the first time in this chapter and see how we can create a few more classes and group them into
namespaces. Then, we'll use these classes, and for that, we'll need to bring in the namespace with a using statement. Please notice that we are continuing with our regular demonstration application here. We're not using the standalone application that we closed the last chapter with. So we're looking at the Employee class, and it is in the namespace. Remember that I showed you earlier that if you go to the Properties of your project, there is this default namespace, which is set to, and automatically, all classes that we add in the root of this project will be part of that namespace. But a application will actually get quite a few classes, so we'll typically start dividing them in folders and also in namespaces. Let me show you. What I'm going to do is I'm going to add here a new folder, and I'm going to name that HR. I'm going to simply move this class into this folder, and I'll do the same with the EmployeeType, by the way. I have organized my project a bit better. And the project is a bit more structured, but my code isn't yet. Typically, I will also change the namespace. I will suffix this also with .HR. It's not required to be the same as the folder name, but Visual Studio will actually do it automatically, as I'll show you in just a minute. The important takeaway here is that I now have the Employee class living inside of this .HR namespace. This means that I can now have multiple classes with the same name, Employee, as long as they live in a different namespace. And I should also do the same with EmployeeType, probably. Indeed, I want to make sure that that also lives in the HR namespace. If you go to the Program class, we'll now see some errors appear in here. Visual Studio I don't know Employee anymore. I couldn't find that. That's because this class, this Program class, lives in the namespace. It doesn't know about the.HR namespace. So again, like we've done before, I'm going to click on the lightbulb and add a using statement for the HR namespace.
By bringing in this using statement, and thus the namespace, these classes are now known again in our Program class. Let us add a few more classes here. Let's say that in our application we also need to have classes that have to do with accounting. So probably I will put them in a folder. Let's called it Accounting. And in there, I'll place all the classes that have to do with accounting. We will now add a new class, and let's called it Account.
Notice that the default behavior of Visual Studio is actually what I've done before. It does add .Accounting to the namespace. And let's make this class a bit more useful. I'll put in a little bit of code to just store the account number in a private field and also create a property again. And while we're at it, let's also bring in the Customer class. Let me quickly do that. I'll show you the results. Here's the Customer class, which is also now part of the Accounting namespace. Let us now go back to the program and create a new customer. And now I have two options. Again, I can bring in the using statement, but I can also use what is known as the fully
qualified name. I can say BethanysPieShopHRM.Accounting.Customer, customer is a New Accounting.Customer. So instead of bringing in the using statement, I've now use the fully qualified name. That means that I can now bring in the name of the class, so Customer, including its namespace.
And the fully qualified name is often less readable, so I recommend not doing it this way. So remove this again and simply bring in a using statement for Accounting. And then Visual Studio will say, well, I don't need this spot because I know what a customer is. So I can just remove this as long as we have the using statement here at the top of the code. Let us now finally discuss in some detail that static keyword that we have been seeing around for quite a few chapters already. You have now enough knowledge to understand what its function is. When we create a class, we're creating a blueprint. We
can on the class include a variable, such as a name on the Employee class. When we create a few instances, these will use the blueprint, and each object, each instance, can have a different value for the fields. I have three employees, each with a different value for the name. They don't really share anything in terms of data values. However, we can also on the class define a member such as a field to be static. Let me give you an example. Say that we plan on giving our employees a bonus, and that bonus will be calculated based on a fixed percentage, so the same for all employees. If we would create a bonus percentage for each employee, each object would get this value and can change it. Imagine being able to adjust your own bonus percentage. How great would that be? So that's why we actually define the value using static. The value won't be instantiated. It's defined on the class level, and it's shared for all objects of the given type. we can still change the value, but in that case the value is changed for all objects. Defining a value using static basically means it's defined on the class level rather than on the object level. This explains why I could only explain to you static when you have a good understanding of objects and classes. So how do we in code then define a value to be static? Well, we can in code create our bonus percentage using the static keyword. At this point, when we now create an employee, it doesn't receive the bonusPercentage field since that is now defined on the class, not on the object.
An important side note, static fields can't be accessed through the objects. You always have to go through the class. I will show you that in a demonstration. Creating static members isn't limited to just static fields, so we can do more than just static variables really.
Here you can see that I'm creating a static method. This is an interesting aspect. Why would we do that? Well, remember what I said about static, it's defined on the class, not on the object. This means that this method itself is available directly through the class, so I don't have to instantiate an object to access this method, I can just call it on the class. That's what you see happening here. I'm now calling the static method, and notice that I'm calling it on Employee, so the class name, the type, without instantiating an object first. That is not what we have done so far. Normally, we created an object of the type first.
When defining a static method, you call the method on the class directly. I did promise you an explanation on why we used static on the methods we used earlier in this book. Well, here it is. We created methods that we could call from Main. Notice that the Main method, so the one that is automatically invoked by C#, is also static. That's actually quite logical. This method needs to be invoked by the runtime without an object being created. From a static method, you can't invoke methods. The reason is the same, there is no object to invoke it on. So we have our static Main, and from that we called a few methods directly. Since Main is static, the methods we called on the Program class also had to be static. From a static method, we can call other static methods directly. Alternatively, and that's what we've done in this book quite a lot, we can also create an object and call methods on these objects. A static can be a bit confusing, so let's head back to Visual Studio and see things in action. I'm going to show you how to add a static field as well as a static method. Then we'll use these static members. Static
means shared. It points to data that is stored on the class level rather than on the object level. It is shared between all objects of that class. Think, for example, of a taxRate. A taxRate is not bound to a certain employee. It is bound to all employees. It's the same.
It's shared between all employees. It's not useful to have each Employee instance contain that value. We'd rather have that value shared on the class level, and that's what we can do. It's static. So I'm going to bring in a public static double taxRate, and I'm going to set it to 0.15. I'm going to update the ReceiveWage method. In the ReceiveWage, I'm now, instead of directly calculating the wage, I'm going to bring in a local wageBeforeTax. Then I'm going to calculate the taxAmount. Finally, I then say that the Wage, so that will be the property, equals the wageBeforeTax minus the taxAmount. The ReceiveWage method that is now using that taxRate. So that's the static data, the static double value that we have defined here.
That is already defined on the class level, and it's shared across objects. I'm also going to update the DisplayEmployeeDetails method. I'm going to update it so that it also displays the taxRate. I'll show you why that is in just a minute. Let's now return to the Program class. In here, we still about two objects, Bethany and George, and we are calling the PerformWork a couple of times on the Bethany object, then we call ReceiveWage, and then we do the DisplayEmployeeDetails.
The ReceiveWage will also use that taxRate, and that taxRate is shared between all objects, so between Bethany and George. These objects will use that same taxRate, which is stored on the class. I don't specify a taxRate on the employee itself. So let's run this, and we now indeed see that the taxRate is 0.15 for both Bethany and George, and also in the Wage calculation, this value was also used. And the static data is, as I said, defined on the class, not on an object, so that means I cannot say bethany.taxRate. You'll see that this field, taxRate, is not available via the object. If I want to change it, I can still do that, such data can be changed if it's public. If I want to do that, I need to call employee.taxRate. Employee is the name of the type and is really proof that static data is defined on
the type. So I set the taxRate now to 0.02. That is now applied for all objects, for Bethany, for George, as well as for other employees. So, if I bring in a bit of code, it will again ask both Bethany and George to perform some work, we'll see that this new taxRate is applied for the both of them as soon as it's set. So let's run the application again. Initially, the taxRate is 0.15, then I change it, and now it is 0.02 for both objects. Static data is, as mentioned, stored on the class rather than on the objects, but it can also include a static method. A static method is also basically available through the class type. Let's bring in, again, a static method. We've seen them before, right? So here, I'm bringing in a static method, as you can see, it's just a regular method, but it has that static keyword again. In this case, it's just returning void, and it is not accepting any parameters. It's just going to write something to the console. In static methods, I can use static data. As you can see, I'm accessing the taxRate. I can, however, not access, for example, the firstName here. That's because this is a static method. It's defined on the class rather than on an object. There may not be an object to look at its firstName. I cannot work with object data in here. I can only work with static data. What I can now do, however, is call this static method through the class. So I can call Employee.DisplayTaxRate, and that can, again, without an object being created, be invoked. So you see that without using the Bethany or the George object, I can still access the static method. A static method can only work with static data, and it can only also call static methods, and that now may ring a bell. If you look back at the Program class, in here, we have the Static void Main. That is a known method to C#, and it is actually static because there is no object really in place yet.
When an application starts, Main will be called on the Program class. It is actually a static method that is called without there being an instance of an object. From here, I can in fact only call other static methods. That's why, earlier in this book, when we were calling methods from the static for Main, these methods needed to be static. Here, we're creating objects. And on these objects, we can call our regular methods. In C# we can also declare a constant variable value. A constant field is really what the name says, a variable that gets a value which cannot be changed. The value needs to be set upon declaration .You can't specify a value afterwards. Such a variable can be created using the const keyword.
And the reason I mention these here is that const fields are also static implicitly. They're declared on the class level too, and are thus shared between all instances of the class. Here you can see how I'm defining a constant value. Defining a constant is pretty similar to a static variable, but instead we now use the const keyword. Once
defined, it's impossible now from anywhere to change its value. You'll even get a compiler error if you try to change this value. Let us quickly return to Visual Studio and take a look at how we can work with a const value. Let me quickly show you the use of a const. Let's go back to the Employee type, and I'm going to bring in a const here, const maxAmountHoursWorked, and I'm going to send it to 1000. Since this is a const, I need to specify the value upon creation of the field here. I cannot later on initialize its value. When I remove the initial value, Visual Studio will start complaining, saying a const field requires a value to be provided, so I need to set this value. Since this is const, from nowhere can I change this value. I cannot, for example, go to the constructor and say that the max amount of hours worked is 800. Again, Visual Studio will give the red squiggly saying that we cannot change its value because it's const.
Chapter 20 How to Work with null
So far, in our application we haven't encountered null values. The concept of null is, however, something you'll come across, well, on a daily basis in C#. Let's understand null, and see how we can work with it. Let's bring our Employee class into the picture again. I'm going to write the following, Employee employee. I have now created an employee variable, but notice I haven't instantiated anything yet. The variable has been declared on the stack, but since employee is a reference type, it doesn't contain the value.
It's null at this point. It's not pointing to an object on the heap yet. Only when we have an object can we actually start calling methods on the object. Once we instantiate the class, a new object is created on the heap using the constructor, and the employee reference is now pointing to the actual object on the heap. When the actual object hasn't been created, we can't call any methods onto it. , there is no object to invoke it on. The code you see here on the screenshot will actually compile, but it will give you a runtime exception. I'm again creating a variable of the employee type, and then I call PerformWork onto that variable. Since there is no object, we'll get an exception while running the application. C# will throw an exception, and you'll see, in fact, a exception. Indeed, our reference to the object is null.
We can't invoke anything on an object that is simply not there. As said, by default the compiler won't give you a compile time error, and things will only go wrong when running the application. Since C# 8.0, though, it's possible to use nullable and reference types, which can also cause the compiler to raise errors where it believes you are invoking members on null objects. This is an feature. We won't go deeper in that here, because that would take us outside of the scope of this book. It's also possible that once we don't need the object any longer, we set the reference to null. What this will do is effectively breaking the link between the reference and the actual object, and the result is the same as not initializing the object. You can't invoke any methods on the employee anymore, since it's null. The object itself is probably not reachable anymore even. It is actually now a target for garbage collection, which we will discuss after the following demonstration. One more thing. We've talked about null in the context of reference types, so classes in our case, but we haven't touched on null in the context of value types. Can they actually be null? Well, that would be a great question.
Think again of the difference between value types and reference types. Value types can contain their data directly. Reference types point to the object on the heap, and can thus be null. But also, value types can actually be null. Let me explain. C# supports the notion of nullable value types. A nullable value type basically represents the actual underlying type plus an additional null value. Say that we want to create a variable of type int, but it could be that we don't have a value, so setting it to null would actually be what we need in this case. Through a nullable value type, the int nullable value type in this case, this is actually possible. To let C# know that we actually want one of these, we need to suffix the type, so here int, with a question mark. We can both set our variable to an integer value or to null. A regular int would always have a value, but now we're not sure about that anymore. For that reason, on a nullable value type we also get a few helpers, as you can see here. I can now check through the use of HasValue if b is null, or it actually does contain a value. In this demonstration, I'm going to show you how we can work with null at runtime, as well as nullable types. So let me show you that we actually need to instantiate a variable before we can actually use it because there's simply no object created otherwise. And let's create another Employee variable, let's call it mysteryPerson, and I set it to null. On my mysteryPerson, I will call DisplayEmployeeDetails, and let's run the application again and see what happens.
And as you see, the application will throw an exception, and it says here there's a NullReferenceException. Indeed, my mysteryPerson, as you can see here, is null and I'm trying to do something, display the employee details, on null. The object simply doesn't exist so I cannot use it. I cannot access its data through its properties, nor can I invoke methods on it. We get a NullReferenceException. We first need to initialize the object before we can do things with it. Leaving out and null would actually have done the same thing. This has the exact same result, although the C# compiler will actually give you an exception saying that it knows that this object will be null, and you're trying to invoke a method on it. This it will not allow. Although this way we cannot do a lot with objects that aren't initialized, that are null, the concept of null and something being nullable is something very important in C#. Think, for example, of a database, and in a database we have a table with employees. It could be that the HourlyRate is nullable in the database. It could be simply unknown that we haven't null in the database. It might be that we also want to represent that in our code.
So here my rates actually double. That's a primitive type, and primitive types cannot contain null. They can contain only a double value, so within a range. It is possible in C# to make a primitive type nullable, and that will then mean that it still can contain all the original values plus null. And I can do that by suffixing this double rate here with a question mark. So now the rate value here is nullable, and I'm trying to assign that here to a double property, which is not nullable. I need to also make this nullable. And now Visual Studio will also say this also needs to be nullable, so indeed. I also need to surface this with a question mark. So by adding this question mark, this becomes a nullable primitive type, making it possible that it contains just any double value or null. And will definitely come in handy when you work, for example, with a database where the HourlyRate could be unknown. And we can see here, however, that Visual Studio is still giving us an error down here at the bottom. What it's saying here is that we are using the HourlyRate, so the nullable double, in an operation, and on the side it's expecting a double value. That won't work because this could definitely be null. What I'm going to do is I'm going to extract here from the HourlyRate, so from the nullable double, the value. This will give me back the actual double value, and then it's happy again. What could happen is that this fails because I said earlier that HourlyRate could be null, and now I'm extracting the value. It could be that it simply doesn't have a value. So what I'm going to do is go back to the constructor, and I'm going to make sure that if no value is given for the rate, so if null is passed in, I'm going to use a default here, and for that I'm going to use what is known as the null coalescing operator.
I'm going to say that if the rate is null, then use the value on the side of this double question mark operator, the null coalescing operator. So if the rate is null, then the HourlyRate will be set to 10.
Otherwise it takes just a double value. Our discussion about null was actually a great bridge to the last topic of this chapter, namely, garbage collection. What is this all about then? When working with objects, we have the variable which points to the actual object. In a real application, a lot of objects will be created, constantly. C# will keep track of the references for you. , references will become null at some point. Maybe we explicitly set it to null or the variable simply goes out of scope. At that point, the object itself will still exist in memory, but the link is broken. And I just said something that should actually worry you a bit. The object still exists. Indeed, it's still sitting there in memory. And this will happen frequently. Links
will be broken, and over time more and more objects will be sitting in memory without any reference pointing to them. As said, that's worrisome, because memory isn't infinite and if our application keeps doing this, the memory will be filled with zombie objects. To solve this, C# comes with the garbage collection. I say C#, but this is actually a .NET feature, part of the CLR, the Common Language Runtime. What will the garbage collector do? Well, it's a process that runs automatically when the CLR deems it to be necessary? The garbage collector will run through the memory and it will look for objects that belong to our application, and if no reference is pointing to the object, it's considered unreachable and it's removed from memory automatically. So the garbage collector is really what it says. It'll be cleaning up unreachable objects, throwing them away, and clearing memory. The garbage collector is an automatic process, you typically don't have to call it, though, you can invoke it from code. When the CLR notices that it is running low on memory, it will kick off the garbage collector. Over time, the garbage collector has become a lot more optimized. One of the things it does, it's using generations. When an object, let's say, survives a run of the garbage collector, it is marked as such. It'll go on to the next generation. The idea here is that objects that survive the run of a garbage collector will probably be needed for a longer time. Marked objects aren't considered with every run, therefore, optimizing the process. We can invoke the garbage collector manually, but it's not very often that you will need to do so. You can actually call GC.Collect.
Even if you invoke it, it's not guaranteed to run. It is determined by the CLR if it will run at that time or not based on several parameters, such as load on the system. In the final demonstration of this chapter, let's take a look at garbage collection. Though it's a process you typically don't see, I'm going to show you a few things in this area. For the sake of this demonstration, I have removed a lot of the code that we had already in the Program class, and I have replaced it with what you see here. I'm now going to create a large number of employees to try show you how C# or .NET behaves in combination with garbage collection. I'm creating a list of employees. The list type is a type, we've touched on it already before, it comes with C#. Try to bring in the system collections generic using statement at the top, and it's used to store a list of objects. In the for loop, I'm going to create a large number of employees and I'm going to add them to that list. Below that, I'm going to clear the list and I'm going to set the employees back to null. Let's now run this application and see how this behaves. W doing this I'm going to open the Diagnostic Tools in Visual Studio. You can find them under
Debug, Windows, Show Diagnostic Tools. I'm going to let this run, and you'll see what happens with the memory on my machine.
At this point, the application is done executing, and what we see here is a quick increase in the start of the application. There's a lot of objects being created, but notice here these yellow ticks at the top. These are all runs of the garbage collector. The .NET runtime actually triggered the run of the garbage collector quite a few times to see if our objects were actually all needed. But since these objects were still referenced, the part of the list, the memory, can't be reclaimed. But you see that here after clearing that list, the objects still aren't removed. That's possible. The garbage collection isn't always running either. It's not constantly fired by the .NET runtime. I can, however, trigger it, and then you'll actually see a drop in memory. We will actually see an additional run at the end, and that will clear the memory, because our objects are in that list, but that list is then cleared. Then the objects aren't referenced anymore and the objects will be cleared, hence we get a lot of extra memory available again. Let's run it again, and see the result of adding this. So now you see that I forced the garbage collection to run at the end, and now the garbage collector was able to remove a lot of
objects from memory that weren't being referenced anymore, hence, the memories of my application goes down again dramatically, and that's what you see here. Now, do remember, it's very rarely needed to actually invoke yourself. In normal circumstances, the .NET runtime will take care of memory handling and clearing unused objects from memory for you. We've reached the end of yet another chapter, and we've extended our knowledge on classes quite a bit. We looked at namespaces and we've learned that their main goal is grouping classes to avoid name conflicts. We've also seen static members. Making something static means that it's class level instead of object level, and we can thus access it through the class as well. We finally looked quite a lot at null. We've understood that unless an actual object is being referenced, we can't invoke methods or access properties; otherwise, we'll get runtime exceptions. And we've also seen the garbage collection process, which cleans up unused objects from memory.
Chapter 21 Inheritance Fundamentals
Classes allow us to model structures like an employee or car, and on these classes we then add fields to store data and methods to add functionality. For objects, relations exist between different types of structures, such as a manager being a specific type of employee or a truck being a specific type of car. This relation can also be applied on classes to the use of inheritance. In this chapter, we'll look at inheritance in C#. We'll learn how we can add this onto our classes and what are the benefits of doing so. Inheritance in C# is an important and very commonly used technique. Let us start with understanding the specifics of inheritance and why we should use it. Once we have understood the use case, we'll learn how we can add inheritance on our classes by creating a base class and a derived class. Next, we'll learn about polymorphism. I can already tell you that polymorphism is a very common interview question, so it's definitely something you'll need to get your head around. Once you have a good understanding of all this, we'll dive a bit deeper and learn about abstract and sealed glasses. Finally, I will show you that everything in C# is an object and it's really visible by looking at the base type for everything, System.Object. Let's kick off by understanding what inheritance is, and once we understand the concepts, we can apply to our classes in the next topic. Let's return for a minute to our business case where we have worked with employees. Let's for a minute, think again of the situation, not in C# code yet. It's a pretty broad term. And we look at Shop, all people
working for the company, and all their employees have a number of properties like a name, but employees will have a different role within the company. Although they're all still employees, some will be a store manager and will probably have a few specific functions, a few will be managers, there might be also a salesperson, and to figure out new pie flavors, we'll have a few researchers working for us as well. Because of their different role, they will have different tasks and perhaps as a company, we'll also be storing different pieces of data for them. But they're all employees and they all have a name, and yes, they probably also share quite a bit of functionality. They all work for us, they all need to be paid, and so on. What I'm introducing here is inheritance, all the different roles, so the manager, the salesperson, the researcher, and so on at Shop, are all employees in the end, and we can also treat them as such. Because of this, they will share a lot of their functions as well. When we make the move to C#, inheritance is a very important concept when applying object orientation. What we will do is create a new class typically, and that will contain the base functionality. That will be, in our case, the employee class. Different other classes will inherit from this base, and because of this, they will share this functionality with the employee class. Indeed, managers are also employees and many functions are the same, but probably not all. A manager needs to make decisions, needs to attend management meetings, and the like. That is specific and that will be brought only of the manager's functionality. This tree, this inheritance tree, we will also model in our application. Inheritance works with parent and derived classes. The parent class, or the base class, is the one that contains the shared functionality, whereas the derived class, or the child class, will add specific functionality. You'll hear the terms base class and parent class being used intermittently as they are really the same concept. In our case, the employees, the parent, and the manager, researcher,
and salesperson will be modeled as derived classes. By default, when they inherit from the base class, they will share the functionality defined on the parent. One of the advantages that comes out of inheritance is that we will be able to reuse code. Instead of giving all the inherited classes the same functionality, we can now add it onto the base class, and it can be reused from there. That is a huge win. It also means that if we need to make a change to common functionality, it has to be done only in one place. Our code therefore becomes easier to maintain. I've so far mentioned the relation between employee and classes like manager, so parent and derived classes, but it can also be multiple levels deep. We could, for example, derive from the salesperson yet another class like junior salesperson, which is again more specific. Now that we understand the concept of inheritance, we can now apply this to our code, and I'm going to follow the example I've just laid out. I'm going to create a base class and a derived type in C#. This way, we'll understand how inheritance works in C#. Being an language, C# fully supports inheritance. It is, as said, one of the cornerstones of Here you can see how we can let C# know that the class needs to inherit from another class. At the top, we have the BaseClass, and as you can see, at least on the class declaration, there's nothing special going on. Then, the second one, the DerivedClass, it's a bit different.
It needs to point to its direct parent, and this we can do by adding behind the class name a colon, and then the name of the BaseClass. The colon, and then the BaseClass name, are the way to create inheritance in C#. Doing this has quite a few consequences, as you'll come to understand. When we apply this on our business case, Employee will be the BaseClass, and we don't need to make any changes to that class at this point. Then, we'll bring in another class, Manager, and that will inherit from Employee.
It has a colon and then Employee as its base type. Manager is now the DervicedClass, and Employee is the BaseClass. As said, introducing a BaseClass has a number of consequences, which is pretty much why we would add inheritance.
The Employee class will contain a functionality that all inherited types will gain because of inheriting from it. This means that the name field and the PerformWork method here, defined on Employee, will become available for Manager, as if they were part of Manager directly. That is what you see here. Manager itself now contains a method called DisplayManagerData, which for now will just display the name of the manager. But notice that Manager doesn't contain the name field. No, it is defined on Employee.
But we can use it on Manager because we inherit from Employee. Members defined on the BaseClass will now become accessible for the DerivedClass. Now, that last sentence does require some extra explanation. The DerivedClass can access some members of the BaseClass. This is where access modifiers start playing an important role. Remember the three most important modifiers. Public and private we've already looked at briefly, and there's that other one, protected. When using inheritance, the DerivedClass can access the public members from the parent. No questions asked. However, members defined as private on the BaseClass are not accessible, they are internal to the class, and can't even be accessed by inheriting types. So if you want the DerivedClass never to access a certain field, property, or method, just make it private in the BaseClass. Protected is somewhat in the middle. When using inheritance, protected members of the BaseClass are accessible from the inheriting type. So, for a DerivedClass, public and protected mean the same. They can access protected members just like they would access public members. However, protected members cannot be accessed from anywhere else. So for types, protected members are private. Let's see these access modifiers in action before we head back to Visual Studio. Notice now that I have made name private on the Employee. Private means mine and mine only. No one can see or use this member. Our trusted Manager class, although it inherits
from Employee, now won't be able to access the name field any longer. You'll even get a compile error saying that, indeed, name is private and cannot be accessed from a DerivedClass. However, making name protected again does allow the Manager class to access it. Protected behaves like public for inheriting types. For anything else, it behaves like private, so the member will not be accessible. We've been talking for quite a while now about inheritance. Time to see it in action. Let's head back to Visual Studio and see how we can turn our employee into a base class. Then, we'll create an inheriting class, and finally, we'll learn about the access modifiers and what influence they have. So far, we've used the employeeType variable and the EmployeeType property to distinguish between the different types an employee could be.
And we used for that the EmployeeType enumeration. But now in this demonstration, we're going to introduce, well, different types, different classes, really, to represent the different types of employee such as a manager, a salesperson, and so on. So, I'm going to refactor my class first. Refactoring means, well, changing things a bit so that I don't need that employeeType variable anymore. That is going to give me some errors.
As you can see here, I'm also going to have to remove the EmployeeType here, and I'm also going to remove that here from the constructor. And finally, I'm going to also remove it here. I seem to be having another error in date inside of the DisplayEmployeeDetails. I also need to remove this reference to the EmployeeType property as well.
Visual Studio now says that the EmployeeType enumeration, well, isn't used anymore, so I can actually remove this, and then things should compile fine. So I'm now at the point where I have my Employee class, and I'm going to represent different types of employees through different classes. And for that, I'm going to use inheritance. So, inside of this HR folder, I'm going to add a new class, and that's going to be the Manager. In fact, the manager is very similar to an employee. A lot of the functionalities that we have defined so far such as the hourlyRate, the firstName, but also the methods such as PerformWork, ReceiveWage, StopWorking, well, they also apply for a manager. That is why I'm going to introduce inheritance here. I'm going to let the Manager class inherit from the Employee class. I'm going to do that by writing a colon and then the name of the base class. I'm also going to make this class public.
Visual Studio now does throw a small error here, saying that there is no argument given that corresponds to the required formal parameter 'first' of Employee.Employee and then a couple of types. That's a bit of a confusing error. But if you look here, this actually points to the constructor, indeed. What I need to do is I also need to provide in my Manager type a constructor that will then pass data to the base constructor because I can, in fact, not create a manager without there being an employee, really, because every manager is, in fact, an employee. So I'm going to introduce a constructor here, and we do that as we did before. It's going to consist out of the access
modifier public and the name of the class. I'm also going to be passing in a couple of parameters, Let me add those first, and let's introduce again curly braces.
There's still an error that is, again, saying pretty much the same. It is still saying we need to create an employee before we have that manager. So I need to, in fact, invoke the base constructor, and that's going to happen through the base keyword. The base keyword allows me to, call the base constructor, passing in values that are now passed in for the manager to the base type. So I can now, in fact, here say call the base constructor to the one on Employee, passing in the value, first, so that's this one, and I'm going to pass that to the base, so the Employee constructor. I'm going to do the same for last. I'm going to do the same for em. I'm going to do the same for bd, the birth date that was, and I'm also going to be passing in a rate. And now Visual Studio is happy, as you can see. It's now accepting my change. So what I've now basically done to introducing the base keyword is calling the constructor on the base class, so on the employee, passing in the values passed in for my manager creation.
At this point, my Manager class exists and because of the fact that it inherits from employee, I can do a lot of things already with it. Let's go and give it a try. We can create still an employee as we did before, and that is what we see here. So I'm creating a new Employee, again, Bethany. And I can now, in a very similar way, create a new Manager. That is my new type, and let's call that manager Mary. And I'm going to now simply create a new Manager object by calling the Manager constructor, and that accepts, well, pretty much the same parameters, so I need to pass values for these. So We have now created a new Manager object, a new Manager instance, and we named that object, Mary. On our original Employee type, we can do what we did before. We can display the employee details. We can ask our Bethany object to perform work, and we can also call the ReceiveWage method on it. But now, since every manager is, in fact, an employee, I can also own that manager on the Mary object, that is, invoke, for example, the DisplayEmployeeDetails. And this will, , if I now do a Go To Definition on this, show me while the same DisplayEmployeeDetails, as you can see here, in the Employee class.
This method is now available. It's now known, let's say, on the Manager class as well. And in a very similar way, we can also ask to perform work. We can also invoke the ReceiveWage method. And if you run this, we will see that Bethany has performed some work, and we see the details for Mary. Mary has done some work, and now Mary receives a wage. You may be thinking at this point, well, that's fine, but what is the added value? Mary can do the exact same things as Bethany, our employee, so the manager is not really different from the employee at this point. Correct, but I can now extend my Manager type. I can add things here on the Manager class that are not available on the employee. What does a manager do? Well, for example, an added functionality wrapped inside of a method, that is, is attending a management meeting. So it could be that our manager needs to perform an action that is not available for an employee such as attending a management meeting. Management meetings tend to run very long, so I'm going to increase the number of hours worked with 10. By doing that, I've introduced something that you may not have noticed yet. The
NumberOfHoursWorked is now used within the Manager type, but it was also available on the base class, so on the Employee.
And because this is public in the Employee class, I can directly work with this in the Manager type, as well as if it were defined on the manager itself. And let us add some console output here to display that our manager is attending the management meeting. And I'm doing pretty much the same thing here for FirstName and LastName. Those two are the ones defined on the base Employee class. I can go back to the Program class, and I can actually let our manager, so Mary, attend a management meeting. That is now a functionality available only for managers. Indeed, I cannot ask Bethany to attend a management meeting. As you can see here, Visual Studio is complaining and says, well, employee does not know about AttendManagementMeeting. Indeed, that's only available on the derived type, so on the Manager type. Let me return once more to the Manager, and let's talk a bit more about access modifiers. In here, I could directly access the number of hours worked. That's a public property, and I can work with that from a derived type. On the Employee, I also have firstName, and lastName, and email, and the numberOfHoursWorked to the private field. I can, in the derived type, not access these private fields. So if I want to access the
numberOfHoursWorked, I cannot, as you can see. I can access the property, but not the private variable. Indeed, any private variable I create here on the base type is available only within the base type. Derived types cannot access those. They can only access public, as well as protected ones.
Let's work with protected for a second. Let us now say that I want to allow derived types to access the numberOfHoursWorked variable. I can do that by making it protected. This will make this one available for derived types, but only for derived types. From the outside, I can still not access that. Let me show you this. I can now access the numberOfHoursWorked, and I can set that also to +=10. And this is now accepted. That is now a protected field, and that's available in the derived classes. So protected means available in the derived type.
But still from the Program class, I cannot access it. If I now go to my bethany type again and I want to access the NumberOfHoursWorked, you see I only get the one with the capital, so as a public property. The protected data is still not accessible. I've now set my numberOfHoursWorked back to private because I can work through the property in my derived types. Using these access modifiers also applies on methods.
Say that I want the StopWorking not to be available on the derived types, well, then I could make this private, which would be quite weird. But let's say that I now want to from the Manager type access the StopWorking method. I will not be able to access that anymore because it's private, and Visual Studio will say that here as well.
It is inaccessible due to its protection level. If I make it public or protected, then it's available again on my draft type. And let's set this back to public.
Chapter 22 Is-A Relation Basics
What inheritance in C# is bringing is what is sometimes referred to as the relation. Indeed, even in the spoken word, we would say a manager is an employee. The relation is exactly what inheritance is bringing us, also in the behavior we're getting in our C# code.
When we define a Manager instance where the Manager class derives from Employee, all public or protected members defined on the base Employee class become available for Manager. If PerformWork is a public or protected method on the Employee class, because the manager is an employee, it becomes available for the manager. The Manager class can, like we've seen in a demonstration, add new functionality, which is then only available for Manager instances, not for Employee instances. And multiple types can inherit from the base Employee class. If Researcher 2 inherits from the base Employee class, every Researcher 2 is an Employee, and therefore Researcher instances will get access to PerformWork as well.
Let us return to Visual Studio and work with multiple classes that will inherit from the employee base class. Because they inherit, we can use the relation and get access to that base functionality. I've now gone ahead and added a few extra types here. Let me show you. I've now added the Developer type, which is a very simple one. It's also deriving directly from employee. And I've also added the StoreManager. This shows that we can actually have multiple classes that derive from the same base class employee. But we can actually go a bit further. Let me add one more. I'm going to bring in the Researcher class. Here's my Researcher class, which adds the ability to research new pie tastes.
What this is going to do is going to accept the number of hours put in research and it's going to add that to the number of hours worked. So in that view, it is still pretty similar to what we had with the manager. We added a new functionality and new method on the derived type, Researcher. Now we can, with inheritance, go multiple
levels deep. For example, I can introduce another class, the JuniorResearcher, which does not directly inherit from Employee, but it inherits from the derived Researcher type. So now I have JuniorResearcher, which inherits from Researcher, and Researcher, in turn, inherits from Employee.
So it can definitely go multiple levels deep. And we can use the JuniorResearcher, well, in a very similar way. Let's go back to the Program class, and I'm going to introduce another object here. I'm going to call that bobJunior, and that's our JuniorResearcher. I initialize that again through its constructor, as you can see here. Because of the fact that bobJunior is a JuniorResearcher, I can ask him to research new pie tastes, and that was a method that accepts a parameter of type int, so I can ask him to put 10 hours in research work. And because bobJunior is an employee, he can receive a wage just like an employee. Indeed, public and protected members cascade through the hierarchy. It's not stopping at the first level of inheritance. Let's run the application to see that everything works fine. Here we now see that Bob has invented a new pie taste. He has worked for 10 hours on that and receives a wage because of the call to receive wage. Now that we have a good understanding of inheritance in C#, let's look at polymorphism, another important pillar of object orientation. We now have created a few classes and applied inheritance.
It's clear that using inheritance we can reuse code. Functionality defined on the base type, like you see here with the PerformWork method, is accessible from the inheriting types, and that allows us to move common functionality to the base class. Thanks to inheritance, we can create a Manager and call the PerformWork onto it, defined on the base Employee class. Similarly, we can create a Researcher and also call the PerformWork on that. Although this is great, the invoked method will always be the same one for all inheriting types. You may be thinking, wasn't that the goal of this all? Well, yes, but what if we want to have a modified version of PerformWork for perhaps a few inheriting types? This is where polymorphism comes in. Polymorphism will allow us in C# to provide a different implementation by an inheriting class for a given method. The name polymorphism indeed says poly, which is multiple, and morph, which is form. We can give a different form to a method in the inheritance tree. Polymorphism is tightly linked with inheritance. Instead of just using the base class method, we can in an inheriting class define a new implementation. The base class would actually indicate that an
inheriting type can give its own implementation. And to use it, there are two new keywords we'll start using in this context, virtual and override. Let's see them in action. Here we have our sample again. Since a manager might need to do a specific task, the implementation of PerformWork might be different than the generalized one that we wrote earlier on the Employee class. What we should do now is recognize this in the Employee declaration. We should say, well, here's an implementation that all inheriting types can use, but you can provide a different one for inheriting types.
This we do by marking the PerformWork method on Employee as virtual. This is a signal for the C# compilers saying that types can provide their own implementation, and that should be used if available. On the right snippet, you then see Manager, which needs a different form of PerformWork. To create this, we'll use the override keyword now, stating explicitly that this version for the Manager type defines a new version. Whenever we now use a Manager anywhere in our application, this specific version will be used. You can see all of this in a diagram again. We have Employee, which defines a virtual PerformWork, and we have, again, two inheriting types, Researcher and Manager. Manager defines an override, so its own version, while Researcher does not. Let's now see through polymorphic behavior which version of the method is called when we have a list of instances. Say that we have an employee instance. In that case, the
virtual PerformWork on Employee will be called. Next, we have a Manager. C# will see that the Manager class has its own version, so it will use that. And when using this instance, we don't have to indicate this. C# will always take the most specific version. If we call PerformWork on a Researcher instance, can you think which one will be used? Indeed, the one on Employee, since that is simply inherited. What polymorphism will then also bring us is the ability to reference an instance of a derived type through a base type reference. And let me explain that one. Take a look at the first line of code you see here on the screenshot. I'm creating a new Manager object since that's the part on the right responsible to create the actual object. But then on the left, I create a variable of type Employee. I'm using a base type reference to point to an object of a more derived type, and that is possible thanks to polymorphism as well. On the second line, I'm doing the same for another type, Researcher, which we now know also inherits from the Employee type. We can now on the Employee references invoke members defined on the Employee class, such as PerformWork.
But again, through polymorphism, the most specific version will be called. So if the Manager class has an override for PerformWork defined, that one will be called, even though we access it through a base type reference. If AttendManagementMeeting is defined on the Manager class, we can't access it through a base type reference,
since that is simply unknown to the Employee type. Why is this useful, you may be asking. Why would I treat my more derived instances using their base type? If you have a group of Managers, Employees, and Researchers, and so on, we can in fact treat all of them using their base type, so Employee. Say that we put this set in an array, and we then want to invoke PerformWork on all of these. We can simply loop over the array and call our PerformWork method on them. C# will always take the most specific implementation based on the type of the actual object. If the actual object our Employee reference points towards is an Employee, the virtual base implementation is used. If you're pointing to an actual Manager, again, then the implementation is used. This is very similar, indeed, to what we had before, but now we treat all objects using a base type reference. Let us return to our sample and bring in virtual and override. We'll then use a number of instances to see which version is being used to the polymorphism. At this point, all our derived types inherit from Employee. It has been decided at Shop that all employees are eligible to receive a bonus. And we can define that functionality on the base type. But maybe, depending on their job title, they may receive a different type of bonus. Let's see how we can use inheritance and polymorphism to tackle this problem. I could, for example, say that on the base type, I'm going to create, let's say, a default implementation, which may work for pretty much all the employee types. Let's do that first. I've introduced, very similarly to what we already have, the GiveBonus method, which gives that employee a bonus.
This, just like the other methods, make this GiveBonus functionality available for all derived types. And this GiveBonus is now available everywhere. I can, for example, to Mary, give her a bonus. But like I said, GiveBonus might not be the same for all derived types. So I may want to let the derived types, such as Manager, give their own implementation. I haven't done that yet. So far, all the functionalities were the same in the derived types. If I want to do so, I need to make this method virtual. Virtual basically says this is the default implementation GiveBonus. If a type, a derived type, that is, does not give a different implementation, this GiveBonus is used. Otherwise, if a more specific one is available, that one will be used. So in virtual does not make the GiveBonus unavailable to derived types. It still works the same at this point. But let's now go to the Manager. And let is on the Manager override the GiveBonus functionality. I'm going to create a different implementation of the GiveBonus method on the Manager type. How are we going to do that? Well, I can write here public, and then override void GiveBonus, and here I can write my own implementation for GiveBonus. We now have an implementation for the GiveBonus method specific for managers. And depending on the type of the object we are going to call the GiveBonus method on, a different one will be used. If the type of our object is a manager, this GiveBonus will be used. Otherwise, the default, defined on Employee, that is, is going to be used. This is polymorphism. Polymorphism allows us to give different forms to the same method. And based on the type of the object we called it on, the default, or the more specific one, will be used. Let's see this in action. Let's go back to our program. Bethany was an employee.
So, Bethany has done a great job, so I'm going to give her a bonus. Then, I'm going to also on Mary, our manager, call GiveBonus. And finally, let's also give Kate, our store manager, a bonus. Now, although we'll see different outputs, I'm going to put a few breakpoints. I'm going to put a breakpoint in the Manager implementation of GiveBonus, one in the Employee implementation, and let us also put a few breakpoints here, and see what the flow of execution is.
Then we go and hit the first breakpoint, Bethany. That's an employee. Pretty easy to understand that this is going to call the GiveBonus on the base type. We are going to call GiveBonus on Mary. Mary is a manager. So now C# notices, hey, we're calling GiveBonus on
Manager. That has a different implementation, a different form for GiveBonus, hence, I will call that one. This is polymorphism. And now we call GiveBonus on Kate. Kate is a store manager. We did not add an override for the GiveBonus to the StoreManager type.
Indeed, we do not have it. So we fill out the application. Continue executing. We see that the GiveBonus in the base Employee type is being called. So through polymorphism, using virtual and override, we can give different forms to the same method, allowing us in the inheritance hierarchy to give different implementations to the same method. Let's try something else. Let's go back here in the Program class, and I'm going to remove all this code for a minute here. I'm going to create an employee array, so an array of the base type, and in there, I'm not going to put only employee objects.
No, instead, I'm going to put Bethany, so that's an employee, in there. But also Mary, our manager, is added to this array, as well as bobJunior, so the junior researcher, and Kevin and Kate, our store managers. What's happening here is that again through polymorphism, I can use a base reference, so a variable of the base type employee, to point to an instance of a derived type. Next, I can now loop over this array, and on each of these instances, I can now invoke the methods defined on the base type, as you can see here. This includes PerformWork, ReceiveWage, DisplayEmployeeDetails, and GiveBonus. This is all defined on the employee base type. Notice I have AttendManagementMeeting in there as well, but have commented it out. Why is that? Well, that's unknown to the Employee type. It's only known to the Manager type. Hence, I can't call it through an employee reference. What should amaze you a bit, is that the GiveBonus method that will be called will depend on the type of object, although we are calling it through an employee reference. So, for the Mary object, the one on manager will be
called. C# will always invoke the most specific version, even if we call it through a base type reference. Let's try that. GiveBonus is called an Employee, that is for Bethany. Next, GiveBonus is called on Manager for Mary. Hey, C# noticed that Mary was a manager, and it called this override. And when we continue, all other instances will result in the call to GiveBonus on the base type, so the one on employee. So, through polymorphism, we can also treat more derived objects through a base type reference. Loop over them, like we did here, and then C# will know which version, which override, I should say, needs to be invoked. So far, we have worked with regular classes to work with inheritance. There are other options that I want to highlight here, namely sealed and abstract classes. I'm going to show you sealed classes directly in a demonstration, as it's a pretty simple concept. While doing so, we are going to try inheriting from sealed classes, which will not be a big success, as you'll see. Say that for some reason we don't allow others using our class to derive from a class, for example, developers. They're always a bit special. So let's say that no one can actually derive from the Developer type. I can actually make the class sealed. This prohibits anyone creating a type that derives from Developer.
I cannot, for example, now, create a JuniorDeveloper class. I can try and see. I'm going to create a class, JuniorDeveloper, and say that this inherits from Developer. As you can see, we're getting a red
squiggly, and that is because the base type, Developer, is sealed, and it's impossible to inherit from a sealed class. This could be useful if you are, for example, creating a UI component, which will also be a class, for example, and you do not want anyone else deriving from that and repackaging your classes. Then sealed really makes sense. You block other developers from deriving from your class, from your type, and creating their own implementation off of that.
So far we have introduced inheritance, and we modeled our hierarchy so that we could move certain functionality to the base class, Employee. That is a very common thing to do. And it may be so that the base class becomes something abstract, something that's more of a concept. Maybe an employee as a concept does not really exist, and we don't want the users of our classes actually instantiate an employee object directly. That too is very common. In that case, we still have the Employee class, it still contains the Name property, since that is shared, and it may contain more than just that, but we may want users of our classes to only instantiate managers, researchers, and the like. Employee could be too much of an abstraction to use. In that case, we can look at using abstract classes in C#. Now what is an abstract class then? An abstract class in C# is used to indicate that we're modeling a concept, something that's abstract. An abstract cause can contain implementations, so methods, but it can also contain methods without an implementation. This is what I mean with abstract. Maybe we simply can't give a default implementation because it's simply not concrete enough. Maybe get paid just can't be done in a generalized way. Well, then we can create an abstract method, so one without an implementation. So the inheriting types, then we'll need to provide one. That's exactly the idea behind an abstract class. For that reason,
simply because not everything has been implemented, they simply cannot be instantiated. The C# compiler simply won't allow you to do so. So how do we go about making a class abstract then? Well, this time we will add a keyword on the class declaration, and that is the abstract keyword. From then on, we can't instantiate the class anymore. Once it's abstract, we can provide methods without an implementation, as you can see here. There, too, we must add the abstract keyword. And since it does not contain an implementation, we need to add a semicolon at the end. Remember, we can include methods, regular ones or virtual ones. That has not changed. So for everything where we think we can provide a default implementation, we can still do so in the abstract base class. As said, because our Employee class is marked as abstract, we can't create an employee instance any longer. The C# compiler simply won't let this go through. Remember that the abstract class is missing parts of the implementation. So it would be very annoying if you would invoke the not implemented methods. C# would not know what to do. , we can inherit from the abstract base class. Here, you can now see the Manager class again, which simply inherits from the employee as before.
It also comes with an implementation for the abstract method using override, as before. This is required. When you inherit from an abstract base class, you must provide an implementation for all abstract members. Alternatively, your inheriting class, too, can become abstract, so its children can then become the real implementation. Let us return to Visual Studio and introduce an abstract class, including an abstract method. Then we'll inherit from this class.
At this point it's possible to create an employee, a store manager, a developer, as we've done so far in our Program class. We created Bethany to be an employee, Mary to be a manager, and so on. But maybe it doesn't really make sense to let the employee be created, be instantiated.
Maybe employee is a concept and not something real, because everyone would be more specific than just an employee, and that we can actually model by making the Employee class abstract. And then we can't create employees anymore; we can only create derived types, which may make sense in some cases. Maybe employee is too abstract to model, but we use it to group common functionality on the base type. But then we cannot instantiate it anymore. We can only instantiate derived types.
Let's go back to our Employee type, and let's make this class abstract now. Abstract is saying to C#, you cannot create a new instance of this anymore. In other words, it's too abstract. It is just a class that is there to model a concept but not something that we could really instantiate. Everyone within Shop is more specific than an employee. If we look at the program class again, Visual Studio will give me an error saying you cannot create an instance of the abstract type Employee. Because I made this abstract, I cannot create a new instance of that anymore. I can only work with, in this case the derived types, which are not abstract. Our abstract base class hasn't really changed. It still contains default implementations for PerformWork, StopWorking, ReceivedWage, and so on. But it could be that it's impossible to give a default implementation, for example, for ReceiveWage, because the wage, the wage calculation, might be different for every employee type. So I may not be able to just give
a default implementation, and that I can model in an abstract class too by making the method itself abstract. I can say it's impossible to create this default implementation so I'm going to make this method abstract.
And now Visual Studio again starts complaining, saying it cannot declare a body because it's abstract. Indeed, I have to remove the default implementation. That's exactly what I said, I cannot give one, and I put a semicolon at the end and Visual Studio is happy again. At this point we've specified in the base type that a wage should be received by any Employee type, but we cannot give default implementation. By putting abstract here, all deriving types such as manager will now give me an error saying that we still need to implement the ReceiveWage method. Indeed, when we make a method abstract on the base class, all deriving types need to give their own implementation. If a class that derives from an abstract class doesn't provide an implementation for all of the abstract methods defined on the base class, it'll need to become abstract too; otherwise, your code will not compile. It thus also can't be
instantiated either. That is, by the way, a common thing to have. In other words, it's possible to have several levels of abstract classes. Only when all abstract members have an implementation can the class be again a regular, class. We can actually, in Visual Studio, click on the lightbulb here and say implement the abstract class. It will then generate a bit of code that implements the ReceiveWage method.
Notice it does this by also using the override keyword, saying that from now on, when a manager is being used, this ReceiveWage method is going to be used, which is kind of logical. We don't even have a default anymore. I can now implement this method here, and this is the specific implementation for ReceiveWage for the Manager type. Or other types such as developer will also now start complaining because they also are missing this implementation for ReceiveWage. Let me add that quickly here.
I have now, on the developer, added the ReceiveWage, as well as on the Researcher and on the StoreManager classes. All types that derive from an abstract class need to provide an implementation for the ReceiveWage method. Finally, let's return to the Program class. We still had an error here because we were trying to instantiate Bethany as an employee, and that is no longer possible. We should perhaps promote Bethany. Let's also make her a manager. If we look again at our array that we had in our previous demonstration, that is still an array of Employee instances, and we are indeed calling the ReceiveWage method on that. Again, through polymorphic behavior, when we invoke the ReceiveWage method, it will now invoke the one on the derived types. There is none left on the base type anyway. So
let's run the application and see that everything works fine again. We see that all of our employees have received indeed their wage. The method on the type itself is now being invoked. So you're going to use abstract classes when you want to model a concept that you cannot really instantiate, but you are using it to group already common functionality. And all methods that you cannot provide a default implementation for, mark them as abstract like we have done here on the employee with our ReceiveWage method. And all the derived that will need to provide an implementation. You can see an abstract class as a contract between the base type and the derived types. By deriving from the basic class, we'll need to provide an implementation, and that really is the contract. By making a method abstract, you know that all the derived types will give an implementation for ReceiveWage. In the final part of this chapter, we will look at how System.Object is the base class for everything in C# and .NET. All types actually derive from another class, System.Object. Indeed, every type, as well as every type we create inherits from that root type. But we haven't written everywhere: Object. Indeed, that's the default base type, and it's not required to put that in. You could add it, but it's not required, and most of the time, you will not do this. If we look up the System.Object class, we can see that it is, in fact, just another class with the constructor and other members.
The members on the System.Object are available on any type, since indeed they all inherit from this type in the end. This means that on any instance we have created, you can call, for example, the ToString method, which will give a string representation of the type. The GetType method will return you on the object you called as on the type itself. This can be useful in code where you dynamically are
working with objects of different types, and based on the type, you want to trigger a different implementation. Aince System.Object is actually the base type of everything, this means also that literally every object we can create can be referenced through an object reference. Let's return once more to Visual Studio and understand that, in fact, everything inherits from System.Object. All types that we create that do not have a specific base class defined inherit from System.Object. Let me prove that to you. Here's our employee class. Again, we can on that and say Go To Base. Visual Studio will see that we don't have any custom type as the base class, so that means it inherits from the System.Object base class. That defines a number of members, such as a constructor and a couple of methods, as you can see here.
The most commonly used one here is the ToString. The ToString method you may have already seen when working with C# a bit, and it actually creates a ToString implementation. It is a virtual method on the object base class, so anything we can create, we can invoke the ToString on. Here's our employees again. Let's say that for each
employee, I also want to write to the console the ToString implementation.
That ToString implementation, we get for free. It is defined on the base class, the System.Object class, and anything that inherits from System.Object gets access to this method. So I can just invoke ToString, and that will give me a string representation. Let's take a look at it. The ToString implementation of a type such as StoreManager is what you see here. By default, C# will show you the name of the type.
That's why you see here StoreManager for Kevin and for Kate and why you see JuniorResearcher for Bob, Manager for Mary, and
Manager also for Bethany. Since every time inherits from System.Object, everything is an object. That means like I write the following, I can use a reference of type Object, and let's call it o1, and I can point that to a new Manager. Indeed, everything is an object, including our Managers as I can use an object reference to point to a Manager. Now, since o1 is a reference of type object, I can only use the members defined on the base object type. I cannot, for example, on o1, call the GiveBonus method because that's defined on Manager and not on object. Only the members available on object can be used through the object reference. This was a long chapter, but we have covered a lot of ground. Inheritance is a very important concept, and it's used all the time when creating C# applications. Through inheritance, we improve our code a lot. It allows us to introduce reuse. We've seen that the access modifiers can be used to decide which members can be used by the inheriting types. Through polymorphism, we can then also provide in these inheriting types a new implementation. You've seen how the virtual keyword is used in the base class and override in the derived type. Finally, we have seen how, in fact, every type itself is already using inheritance since everything derives from System.Object.
Chapter 23 How to Use Interfaces
In this chapter, I'm going to talk to you about another category of types in C#, interfaces. This chapter will serve as an introduction to interfaces. First, we need to understand interfaces, what are they, and what is their use. Next, I will show you some of the interfaces in C# and .NET, and we'll see how we can use these. Finally, you'll see that interfaces and polymorphism also go hand in hand. Before we can start looking at interfaces, it's important that you understand what they are, so let's do that first. A few chapters back, we talked in depth about the CTS, the common type system. This was a standard for the different languages in the .NET ecosystem to use a number of constructs known to all languages, and because of this, we can exchange code between them. You've seen enums, structs, classes, and now in this chapter we'll look at interfaces. Indeed, interfaces are a different category of types, so they're definitely important to understand. As mentioned, we won't be talking about delegates in this book. So what are interfaces then, and why are they so important in C#? Let me explain. Interfaces are contracts. Think about a contract for a minute in real life. What does it do? When you sign a contract with someone, it will bind you and probably another party to follow, well, a certain set of rules. We're all familiar with this concept. Well, an interface is pretty much the same, but then in code. It will contain typically a set of declarations of related functionality. This could be a list of methods, for example. A class can indicate it will implement that interface. In that case, all members defined in the contract for which no default implementation
exists must be implemented as per contract. Well we now want to use that class that implements the interface. We know for a fact that the methods are implemented, that is the contract. We can rely on the fact that they have received an implementation, and we can build our code around that. Creating an interface is actually pretty simple, and it will all evolve around the interface keyword. Interfaces are contracts. They contain definitions for a group of related functionalities. Typically, interfaces do not contain implementation code, it is up to the implementing classes to provide that. Interfaces can provide default implementation that will be used if the class does not provide one. However, this feature has additional concerns that we won't be discussing in this book. This feature was introduced in C# 8. Doesn't this all sounds a bit familiar? Indeed. Abstract classes also contain methods that don't have an implementation. Just like abstract classes, interfaces also can't be instantiated, and that seems logical. They might contain members without an implementation, so it's impossible to invoke methods on an instance of one since these implementations might simply not exist. Finally, interfaces will typically have a name starting with an I. That's a convention in .NET, and things will work fine if you give an interface a name that does not start with an I. This is mostly done to make it easy to see what is an interface and what is a class just by looking at the name. Here's the first interface named IEmployee.
Although it is just a small snippet of code, you can already see the different things I've just mentioned. We're using the interface keyboard, and the name of the interface starts with an I. The interface is nothing but a contract. All types that implement it will need to obey it. They will all need to give an implementation for the methods declared in this interface. In the interface body, we see that it contains just one method, PerformWork. Just like with an abstract method in an abstract class, PerformWork here does not contain an implementation, but again, notice the semicolon at the end. What do I mean exactly when I say implementing an interface? Well, let's see.
Implementing an interface is pretty similar to inheriting from a base class. Again, using a colon you can let a class implement an interface. Here we see the Manager class, and if I want to make sure that the class implements the contract, that is IEmployee or interface, we can make that happen as you see here, implementing an interface, so the contract also ties you to what the contract states. Our class is now assigning the contract, and that means that an implementation must be provided for every method without an implementation in the interface. Otherwise, the C# compiler will
complain. The method implementation is just a regular method though. One more thing before we go to the demonstration. You may have thought, well, this interface thing reminds me a lot of what I've seen with abstract classes, and that would be a correct thing to say. Both abstract classes and interfaces are abstract concepts and neither can be instantiated. Just like abstract classes, interfaces can contain, and mostly will, members without an implementation. So yes, they are pretty similar. There is one big difference that I haven't mentioned yet. In C#, only single inheritance is supported. This means that a class can only inherit from one class at a time, not more. If you have ever worked with C++, this will definitely be different for you; however, when working with interfaces, it's possible to implement more than one interface on a single class. Time to head back to Visual Studio and take a look at creating and implementing interfaces. In the previous chapter we used this array of employees, and we looped over the array, and we called on each instance on each employee, that is, the PerformWork, the ReceiveWage, and so on. We could do this because everything inherits from Employee. Hence, we know that all these functionalities are available. There is another way of doing this in C# and it is through interfaces. Interfaces are contracts.
We define an interface, and types implementing this interface will need to provide an implementation for the contract. Hence, we will also know that these members are available on the implementing types. Let's create an interface. So in this HR folder I'm going to create our first interface. It's going to be the IEmployee interface. You can just use class type here, or, alternatively, search for interface, as it has a different template. I'll use the class type here, and then I'll replace this to become an interface with the keyword interface. Let's make the interface public, and notice that I have followed, in fact, the convention.
An interface will typically start with an I. Now as said, interfaces are contracts, and we talked about contract earlier. Indeed, abstract classes are also a contract. Types that inherit from an abstract base class need to provide an implementation for the abstract methods defined in the abstract base class. With interfaces, we're looking at something very similar. With interfaces, though, we typically do not provide default implementation like we can with abstract classes. Let's create an interface. As you can see, creating an interface is actually pretty similar to creating a class. Let's now add a member, a method, without implementation to the interface, which is what we'll typically do. I'm going to create a method, ReceiveWage, and we won't give it an implementation. It will have a return type, which can be void, and it can define parameters. Since we don't have an implementation, the line will end in a semicolon and so no curly braces. And this, again, looks very similar to what we saw earlier with abstract base classes. Let's add some more members here. On my interface I've now added a few other methods, methods that we already know from previous samples. Let's now go to our employee class. For the sake of this demonstration, I'm using again the Employee class, not the abstract base class we used to a previous demonstration. I have defined the ReceiveWage again as a method on the Employee base class.
What I can do is I can let the Employee implement the IEmployee interface. This now means that the Employee class needs to provide an implementation for at least all members of the interface without a default implementation. We don't have any of the latter, so all methods we have defined in the IEmployee interface need to be implemented here.
At this point, that is the case because on my interface I defined all the methods that I already have on Employee, but let's now add on the interface another method. Let's be friendly to our employees, because that's very important, and let's bring in the GiveCompliment. When they do well, we need to give them a compliment. At this point, Visual Studio will complain and say, well, IEmployee is not implemented. You're not, on your Employee class, implementing the contract defined in IEmployee. You need to provide an implementation for the GiveCompliment method. If you click here on Show potential fixes, you can let Visual Studio implement the interface, and now somewhere at the bottom, typically, it has added this default implementation for GiveCompliment.
I am not going to let it throw an exception, saying that it's not implemented. I'm going to add my own implementation here, saying that you've done a great job, passing in the FirstName. Interfaces are a very frequently used concept in C# and in the base class library of .NET. Let's take a look at some of these, let's say, famous interfaces in C#. There are some interfaces that you'll come across frequently when building .NET and C# applications. The dimension interfaces are a very commonly used concept. These interfaces exist typically so that we can provide the implementation, and based on that, certain functionality will become available. The IComparable interface defines a method that is typically used to compare two objects of a certain type with each other, used, for example, for ordering. The IEquatable interface is used to provide functionality to check equality between two objects. The ICloneable interface is another commonly used interface used to provide a method that clones an object. IEnumerable exists to provide a way to enumerate the items in a list, and related to that is the IList interface, which is implemented by collection classes that allow accessing elements using an index. The IDisposable interface is used to provide a way to dispose of unmanaged resources. These interfaces exist in .NET, and we can implement them on our own types. Certain functionalities, such as ordering or cloning will rely on us implementing the interface and providing an implementation for the method or methods defined in the interface.
Most of these interfaces are very simple. As you can see here, for example, the ICloneable. The interface just defines a method Clone that we need to implement. When then somewhere else a clone of an object implementing this interface is invoked, our code will be invoked. Let's return to Visual Studio and implement one of these commonly used interfaces, IComparable. Then I'll show you what extra behavior we are getting by implementing this interface. I have changed the Employee class again slightly.
I've added the id filter and an id property. What I now want to do is I want to enable sorting. When I give a list of employees, I want to be able to sort them based on the value of this id. C#, or, in fact, I should say .NET, comes with quite a few interfaces, and one of them is the IComparable interface. The IComparable interface comes with the CompareTo method. In this method, I can write logic to compare the current object with another object. I need to write my own logic in that method.
I am implementing the contract, and that can then be used by C# to compare two objects. I'm going to go back to my Employee class, and I'm going to implement the IComparable interface. The IComparable interface, as mentioned, has one method, the CompareTo method, and I've asked Visual Studio to generate it. In this CompareTo method, I'm now going to write logic to compare my current object with the object that is being passed in as a parameter here. Let me add that now. I'm writing logic here to compare the current employee with another object, but I'm going to assume that that is also an employee. So I'm going to cast that object into an employee, and then I'm going to compare the id values. I'm going to check if the id is larger than the one that's being passed in, then I return 1. If it's less than, I return and if they're the same, I return 0. This +1, or 0 is actually a sign for C# to understand whether or not the object being passed in is large or small. Those values it knows, so I need to provide a logic to come to that +1, or 0. Now
by making my employee implement the IComparable and providing the CompareTo method, I've signed the contract that my type can actually be sorted, and that, well, opens doors. Let me show you the Program class now.
I've changed it slightly so that in the constructor of the StoreManager, the Manager, and so on, I now also pass in the id. Let's take a look at the Manager constructor. That now comes with an id parameter because the base constructor, the one on Employee, also now comes with an Employee id. I've defined a few employees, and I've now placed them here in this list.
This is a generic list, and that has the ability to sort. If I ask C# to sort a list of employees, it doesn't know how to do that. It doesn't know if I want to sort them by first name, by last name, hence, I have on my Employee type implemented the IComparable, and I've provided my own comparison logic.
I have, in other words, signed the contract that my type knows how to be sorted. That's why interfaces are important. So, calling the Sort
method on the list of employees will invoke the CompareTo method to sort my list.
Let's put a breakpoint in the Employee CompareTo, and then we'll see that this CompareTo method is automatically invoked when the Sort method is running on my list. It's called a few times to sort all the items, and once it's done sorting, we will see that employee 1 is the 1 with id 100, that's the lowest id, the second one is 748, and the 1 here, the last in the list, has the highest id. So indeed, the sorting worked based on the ids. By implementing the IComparable interface and providing my own CompareTo method, I signed a contract saying that I define a method to specify how sorting needs to work. Interfaces can be used to enforce that types implement certain numbers. In the very final part of this book, I want to show you how interfaces relate to polymorphism. By working with interfaces, say the IEmployee interface, we're also creating a relation between the interface and the type implementing the interface. Just like with inheritance, an relation is established when implementing the interface.
Although we can't instantiate interface types, the code you see here on the screenshot is perfectly valid. I'm using a variable, which has the IEmployee type, so the interface. But through polymorphic behavior, we can use this to point to anything that implements the IEmployee interface. And to top it off, we can on that instance call methods that are defined in the contract, that would here be the PerformWork. In the last demonstration of this book, let's take another look at the polymorphic behavior of interfaces in C#. Interfaces also provide polymorphic behavior. Because of the fact that Employee implements the IEmployee interface, every employee, but also every Manager, every Researcher is an IEmployee. So if I go back to the program class, I can, everywhere where I used Employee, also use IEmployee. Every StoreManager is, as said, an IEmployee, so is every Manager, so is every JuniorResearcher, as well as every StoreManager. So because of the fact that the base type, Employee, implements the IEmployee interface, every Manager is also an IEmployee and can be addressed as such. I do get an exception here because my Employee list here is expecting employees.
I also need to change this to become IEmployee, and then I can just loop through that list again and call the DisplayEmployeeDetails on each IEmployee in our list now as well. Again, what isn't possible is calling the AttendManagementMeeting on Mary, because Mary is now an IEmployee variable, and on IEmployee we only had these members available. The AttendManagementMeeting is added on the Manager type, and it's not available on the IEmployee type. And with that, we have reached the end of this last chapter. We have taken a first look at interfaces, and we've seen that these are used to define a contract that classes implementing the interface need to follow. This means that all methods defined in the interface must be implemented on the class. We've also seen that interfaces are similar to abstract classes, but they have the added advantage that more than a single interface can be implemented by a single class. Interfaces are a very common concept in C# and .NET, and many interfaces already built in. Please check out other books part of the C# path for more information on concepts covered in this chapter. We've now also reached the end of this book. This book is part of the C# path, and you can continue your learning journey to become a C# expert. The only thing left for me at this point is congratulating you on completing this book, and I hope you have learned a lot of new things.
BOOK 5
C#
PROGRAMMING
EXPLICIT INTERFACE IMPLEMENTATION
RICHIE MILLER
Introduction
We all want code that is flexible and easy to maintain and test. Interfaces in C# have been a great tool to help me get there, and they can help you too. I have used interfaces to create a plugin architecture to quickly add functionality. I have used interfaces to build a rules engine for business applications. I have used interfaces to isolate code for unit testing. And I have used interfaces to separate my code from tools that were likely to change. As developers, we want code that is easy to maintain, code that flexes under change rather than breaks. We want code that is easy to extend, code that can respond quickly to new requirements. And we want code that is easy to test. Interfaces can help us add these qualities to our applications. When I first encountered interfaces, they were a mystery to me. I understood the technical bits and I could tell that they were important, but I didn't understand why I would use them. And this went on for quite a while. I was really excited when I went to a conference and there was a session on interfaces. I thought, this is great. I'll sit here for 75 minutes, and then I'll finally understand where to use these things. So the speaker gets up and says, let's say you have an IFoo interface and you implement it with a Bar class. Internally I screamed, no, don't give me a foo bar example. I need a reason to use these things. Needless to say, I did not get the insight that I needed that day. But over time, I did quite a bit
of reading. And I had conversations with some really smart and helpful people. And I spent time trying to wrap my head around the good and bad of abstraction. And I also made a lot of mistakes along the way. What I found is that interfaces are really awesome. This was a long and frustrating journey for me. I do not want other developers to have to go through that same frustration, so we are going to look at the practical bits so that we can understand how to take advantage of this great tool. We will look at the what, why, how, and where of interfaces. First, we'll start out with the what. This includes definitions and some of the technical bits. Then we will move on to the why so that we can see how interfaces can make our code easier to maintain, extend, and test. When we look at the how, we will create our own interfaces and implement them. This is where I've seen the most benefit in my own code. Along the way, we'll see the where. This is the practical part of where we should be using interfaces and also how interfaces provide a good jumping off point to explore topics such as dependency injection and unit testing. By the time we're done, you will be comfortable using and implementing interfaces, whether they are included in the .NET libraries or written by someone else. And you will be able to create your own interfaces to add a layer of abstraction to your code, all with the goal of code that is easier to maintain, extend, and test. This book assumes that you have a basic understanding of the C# language including things such as classes, inheritance, properties, and methods. The samples include specific technologies, such as a ASP.NET MVC, web services, and Entity Framework, but you do not need to understand those details to get the most out of the book. We will use several different sample projects throughout the book. I'm using Visual Studio
2019 Community edition with the ASP.NET and web development workload installed. The sample projects use .NET 5.0 and C# 9. The code shown will also work with .NET Core 3.1, which is the current support version of .NET and also with the upcoming .NET 6. All of the projects will also work with Visual Studio Code if that's the editor of your choice.
Chapter 1 C# Definitions
In this chapter, we will look at the what and why of interfaces. We will start by defining what interfaces are. Then we will move on to explore the differences between concrete classes, abstract classes, and interfaces. They all have some similar functionality, but we'll focus on a few of the key differences. Then we will see how interfaces can add flexibility to our code. When things change in the code, ideally, the code should flex rather than break. Interfaces can help us with that. Here is the definition of an interface from the Microsoft documentation. “An interface contains definitions for a group of related functionalities that a class or struct must implement.” That definition is a bit obscure. One way to think of an interface is as a contract. When a class implements an interface, it fulfills that contract by saying I have these functions. And when we are talking about functions in interfaces, we mean properties, methods, events, and indexers. We will work primarily with methods in our examples, but I found the other types of members to be quite useful as well. To explore the difference between concrete classes, abstract classes, and interfaces, our example will use regular polygons. A regular polygon is a shape that has three or more sides where each side is the same length. So a square is an example of a regular polygon because it has four sides that are all the same length. A rectangle would not be a regular polygon since the sides are different lengths. For example if we have a square and an equilateral triangle. Regular polygons have a couple of features that we can think about. The first is the perimeter. The perimeter is the distance around the shape. The
nice thing about the perimeter is that we can calculate it the same for every polygon. This is the number of sides multiplied by the length of each side. Another feature of regular polygons is the area. This is the space inside the shape. To calculate the area of a square, we multiply the length by the length. To calculate the area of an equilateral triangle, we multiply the length by the length and then multiply it by the square root of 3 divided by 4. The calculation for area varies depending on the polygon. We need to take this into account in our code, so let's look at some code. Now we will look at a quick sample. We will write code for a regular polygon using a concrete class, an abstract class, and an interface. This will give us an idea of the differences in these tools. In Visual Studio, and for this example, we're using the Polygons solution. This has two projects. The Polygons project is a console application, and we'll use this to display some output, and Polygons.Library is a class library that has the details of our application code.
Our goal is to have objects that represent a regular polygon using a concrete class, an abstract class, and an interface. So let's start by looking at the concrete class, ConcreteRegularPolygon. What's a concrete class? It's just a class. I use the word concrete to distinguish it from the abstract class that we'll see in just a bit.
Our ConcreteRegularPolygon will act as a base class so that we can create specific polygons. This class has two properties, an integer to represent the NumberOfSides and an integer to represent the SideLength. These properties are what are known as automatic properties. This is a shortened syntax. To show this, let's create a full property. So we'll comment out the SideLength, and I'll use a code snippet in Visual Studio. So I'll type propfull, which is a code snippet for a property with a backing field, and hit Tab twice, and it will fill in a template for us.
This will be an integer, so I'll hit Tab. I'll use lowercase sideLength as the backing field, press Tab again, and uppercase SideLength as the name of the property, and hit Enter to complete the snippet. This is a full property with a backing field, so if we look at the getter, it returns the value that's in our private field. Our setter will set the value of that private field. This is a common scenario when we don't have to do anything special with a property, so there is the shortened syntax, which is the automatic property. So let's put this code back the way it was.
The reason I'm pointing this out is that we will see some syntax a little bit later that looks like an automatic property, but actually means something else. For now, it's good to understand that the properties we have here in our concrete class are automatic properties. Next, we have a constructor. Using parameters, it sets our two properties, NumberOfSides and SideLength. Then we have the GetPerimeter method. As a reminder, the calculation for GetPerimeter is the same, regardless of how many sides the polygon has, NumberOfSides times SideLength. Because it's the same, we can have it in our base class. Our GetArea method is a bit different. The calculation for area varies depending on how many sides there are, so it will be up to the child class to provide the calculation for this. In our base class, we throw a NotImplementedException. So if a child class does not provide an implementation, we'll get an exception instead. To see this class in action, we'll look at our Square class. We see Square inherits from ConcreteRegularPolygon, so it has all of the properties and methods in the base class. Square
has its own constructor that takes a length, and then we pass that through to the base class constructor, specifying the NumberOfSides, which is 4 for a Square, and the length that's passed into the Square constructor. This make sure that our properties get set appropriately. We also provide our own implementation of the GetArea method by overriding the base class, and the calculation for an area of a Square is SideLength times SideLength. So now, let's go to our console application in the program.cs file. Here, we have code that creates a new instance of Square with a SideLength of 5, and then we display the properties of that. We won't look at the details of the DisplayPolygon method. If you're interested, you can look at the source code for yourself. I'll press F5 to start the application, and we see that we have the output from our Square, the Number Of Sides, the Side Length, the Perimeter, and the Area.
One thing about using the concrete base class is that there is nothing forcing us to provide an override of the GetArea method, and what that means is in our Square class, we can comment out
this code and not provide our own calculation. To comment out blocks of code, I use the keyboard shortcut, Ctrl+K, Ctrl+C. Now we'll rebuild the application using the F6 key, and we see that we have a successful build. But when we run the application using F5, we get a runtime error. When we try to get the value from the GetArea method, we get a NotImplementedException, the exception that's thrown by the base class. So what this shows us is when we use a concrete base class, there's nothing that forces us to provide any implementation code.
Now, let's just put the GetArea method back by using Ctrl+K, Ctrl+U to uncomment a block of code and rerun the application to make sure everything is back where we expect it to be. That we've seen a concrete class, let's take a look at an abstract class to see how it differs. What's the definition of an abstract class? Well, it's a class that has one or more abstract members, not a great definition, but we'll see what that means as we look at the code. So to start with, we have the same properties as the concrete class, the NumberOfSides and SideLength, and just like in the concrete class,
these are automatic properties. We also have a constructor that sets those properties, and we have a GetPerimeter method that returns the NumberOfSides times the SideLength. Things are different when we look at the GetArea method. Notice that there's no body to this method. It's only a declaration.
If we have a class that provides a declaration, but no implementation code, we need to mark that method as abstract, which is what we've done here. And because this class has at least one abstract member, the entire class is marked as abstract as well. What this means is it's up to the child class to provide an implementation for the GetArea method, and we'll see that when we look at the Triangle class. Overall, the Triangle class looks very similar to the Square class that we saw earlier. In this case, it inherits from AbstractRegularPolygon, we have a constructor that calls the base class constructor, and then we have an override of the GetArea
method. And in this case, the calculation is quite a bit different than what we saw in the Square. So let's go back to our console application and see how this works. I'll uncomment this block of code, and here, we create a new Triangle with a SideLength of 5, and then we display the values from that.
When we run the application, we see that we get values for both the Square and the Triangle. So on the surface, it looks like whether we inherit from a concrete class or an abstract class, things are pretty similar at this point. But let's look at one major difference. If I go back to the Triangle class, and comment out the GetArea of method, and try to build the application, we end up with a build failure. And if we on this message, we can see that it says, 'Triangle' does not implement inherited abstract member GetArea'.
So in this case, the Triangle class is forced to provide an implementation for GetArea. If we decide we're not going to provide it, our code will not build. What I like about this when we compare it to the concrete class is I get a compile time error instead of a runtime error. If I have a choice between a compile time error and a runtime error, I'll take the compile time error each time. So, so far, we've seen a concrete base class and an abstract base class.
Let's take a look at an interface. And for that, we'll look at the IRegularPolygon interface. One thing to notice is that the name of our interface starts with a capital letter I. This is done by convention. There's nothing that forces you to do this, but all of the interfaces in the .NET library start with a capital letter I, so I follow that same convention. Our interface is interesting because in this case, it contains declarations only. It does not contain implementation code, and this is where things get a little confusing. Remember how I kept mentioning automatic properties. Well, if we look at the code we have here for a NumberOfSides and SideLength, it certainly looks like an automatic property, but it is not. This is a declaration that whoever implements this interface must provide these two properties. Typically, interfaces do not contain implementation, code. It is up to the implementing classes to provide that. Interfaces can provide a default implementation that will be used if the class does not provide one; however, this feature has some additional concerns. We will explore this in the last chapter of the book. Until then, the interfaces in our examples will not have any implementation, and specifically, with regard to properties, there's no way to put an
automatic property inside an interface. For our two methods, GetPerimeter and GetArea, these are also just declarations. So it is up to the implementing class to provide the NumberOfSides, the SideLength, the GetPerimeter method, and the GetArea method.
You may notice that none of the members of this interface have access modifiers, public, protected, private, or internal. We can put access modifiers on the members if we'd like. If we do not provide them, they are public by default. And just so that you're aware of the difference, for classes, both our concrete and abstract class, the default is private. So if we do not provide an access modifier on a member of a class, it defaults to private. If we do not provide one on the member of an interface, it defaults to public. You will often see interface members without explicit modifiers, meaning that they are public. The reason for this is, prior to C# 8, everything in interfaces was automatically public. We'll talk more about other access modifiers in the last chapter. For now, it's important to understand that the default for interfaces is different than the default for classes. To see the interface in action, we'll look at the Octagon class, and here, we can see that Octagon implements the IRegularPolygon interface. Notice, I said implements the interface, not inherits from the interface. One thing to remember about C# is if
we do not provide a base class, everything inherits from Object. So I could update the syntax to say the Octagon inherits from Object and implements IRegularPolygon. One nice thing about this is that a class can implement as many interfaces as it needs to. Because our interface does not provide any implementation code, we have to provide all of that in our Octagon class. So we have the two properties, the NumberOfSides and the SideLength, and just to make things confusing, these are automatic properties here since it is a class. I really wish the syntax was different between these two things, but unfortunately, this is what we currently have. In addition to the properties, we have a constructor to populate those values, we have the GetPerimeter method, and we have the GetArea method, all with implementation code. If we go back to our console application, we can add an Octagon. This creates an instance of our Octagon class with a SideLength of 5, and when we run our application, we'll see all of the values come out. One big similarity between abstract classes and interfaces is that we must provide implementations for anything that doesn't already have one provided.
That means, in our Octagon class, we must provide all of these implementations. If I comment out to GetPerimeter method and try to build the application, we will get a build error. And if we look at the error, it says, 'Octagon' does not implement interface member GetPerimeter()'. So just like with abstract members in an abstract class, we get a compiler error if we do not provide the implementations that are needed by the interface.
This is just a quick overview so that we can see how concrete classes, abstract classes, and interfaces work. There are several
similarities between abstract classes and interfaces, but there are quite a few differences as well. We'll take a closer look at those differences later on in the book.
Chapter 2 Programming to an Abstraction
Now that we have seen what interfaces are, it is time to start looking at why we want to use them. One big advantage is the interfaces can make our code more flexible. This gives us resilience in the face of change and applications are constantly changing to meet our user's needs. This resilience means that when we make changes in one part of our application, we reduce or eliminate the need to make changes in other parts of the application. Our code flexes rather than breaks. This also insulates our code from the implementation details. We can deal with code at a higher level by focusing on the results of functionality rather than digging into the details of the functionality itself. Of book, we can drill into those details if we need to, but they are out of the way so that we do that only if we need to. There is a general piece of advice that works in many situations, program to an abstraction rather than a concrete type. When we are dealing with abstractions, we are dealing with the higher level code and we can avoid getting buried in the details. In our examples, we will translate this advice as program to an interface rather than a concrete class. An interface is an abstraction, it adds a seem to our code and this is how we can focus on the higher level functionality. We could blindly follow this advice, but that leads to trouble. It is better to understand the reasoning behind it, then we can decide when to follow the advice and when we can safely ignore it. Interfaces are tools and there are times when they are useful and times when they are not. We saw earlier that concrete classes are just the regular classes that we use
every day. These can be classes we create ourselves or classes from various libraries. For our next example, we will use the .NET collection classes. Here is a list of different types of collections that we have available to us.
We have List of T which gives us a collection of objects. We have HashTable, which is an unordered bag of objects. This can give us very fast access to individual items. We also have a queue, which offers a first in/first out collection. We have stack which offers a last in/first out collection. There are a lot of options to choose from. Collections have a number of interfaces that represent different pieces of functionality. List of T implements seven different interfaces. IList of T, IList, ICollection of T, IReadOnlyList of T, IReadOnlyCollection of T, IEnumerable of T, and IEnumerable.
The functionality that we want is an IEnumerable and IEnumerable of T. These interfaces describe the ability to iterate over the items of a collection so we can keep asking for the next item, next, next, next until we get to the end. In fact, there is nothing about the IEnumerable interface that says it needs to be a collection, and we'll see that in a future demonstration. For now, we just need to know that IEnumerable is used by foreach, one of my favorite language features in C#. For our example, we will call code from a library by using a concrete class and by using an interface. Then we will change the underlying type in the library. This will show how the code responds to those changes, whether it breaks and requires fixing or whether it flexes and continues to work even when things change. Often we're working on projects where we have different
developers or different teams working on various parts of the application. This application is a smaller version of that scenario. Our responsibility is the user interaction code of a website. Another team is working on the library that supplies the data that we display. For this example, we're using the solution. If we run the application, we'll see that we have a website that has two links, one to get people using a concrete type and one to get people using an abstract type.
This is an ASP.NET MVC application, and I purposely kept things a little bit boring. This is so that we can concentrate on interfaces rather than getting buried in the weeds of how to build a good website These links don't do anything at the moment, so let's go back to our code and fill some things in. But before we do that, let's take a look at the projects that we have in place. The PeopleViewer project is an ASP.NET MVC application. This is the code that we are responsible for. We'll be getting data from the People.Library project.
This is a class library, and it's managed by another team that's responsible for getting us the data that we need. The People.Service project is a service that provides data that the library uses, and the Common project holds some files that are common amongst all of the projects. Don't worry if you're not familiar with ASP.NET MVC. We will only look at a few files in this project, and we'll make sure that those files are easy to find. Before writing our code, I want to look at the code that the other team is providing for us. This is in the PersonReader class that's part of the People.Library project. Now, since another team is managing the details, we're not going to worry about those. Instead, I want to look at the signatures of the methods that they're providing. And they're providing us with a GetPeople method that returns an array of Person objects and a GetPerson method that takes an id as a parameter and returns an individual person.
Let's take a peek at what this person looks like. So I'll and choose Peek Definition, and this shows us a class that has six properties, which is the shape of our data and an override of the ToString method.
So let's use this GetPeople method in our own code. For this, we'll go to the PeopleViewer project and open up the Controllers folder and go to the PeopleController class. This is one of the classes that holds code that we're responsible for.
At the top of this class, we have a field to hold a new PersonReader object. That's the library class that we just looked at. And then we have two methods, UseConcreteType and UseAbstractType, that line up with the links from our website. So let's start by filling in the code for the UseConcreteType method. To do this, I'll take a look at what's coming out of the library. So if I say reader.GetPeople and then hover over this, we can see that this method returns an array of Person objects. Since we're in the UseConcreteType method, we'll create a Person array called people, and then assign it to what's coming back from that method.
So our people variable is a Person array, a concrete collection type. To use this variable, we'll pass it into our View, and we'll see how that works in just a moment. And this is enough code to get this working. I'll build and run the application using F5, and we'll actually see two windows pop open, and then our browser.
Those two console Windows that pop open represent the web service that provides the data, as well as our PeopleViewer application, the one that we're working on. The solution in Visual Studio is set up so that those both start up automatically. But how is this people variable used inside the View? And for that we'll go to another file. In the Views folder of the PeopleViewer project, we can go to the People folder, and then look at Index.cshtml. Index.cshtml is a Razor view, and this is a combination of both C# and HTML.
When we pass the people variable, it shows up as the Model property in the View. So if we look at the foreach statement, we can see that we're iterating over each item in the model and then displaying the data. The only thing we're doing with this model is foreaching over it. We're not trying to index into it, we're not trying to modify anything in it, so let's take a look at what foreach actually needs. If I click on foreach and then press F1, it will open up help, and this tells us that foreach works on anything that implements the IEnumerable or IEnumerable interface, and that interface is an abstraction that we can reference in our code. In fact, if we look at the View code, at the top I've set that the model is an IEnumerable, so the view itself is referencing just the interface and not any particular concrete collection type. And we can do the same thing in our PeopleController code. So let's do that by filling in the UseAbstractType method. The type of the variable I use here will be an IEnumerable, so we'll use an abstraction rather than a concrete type. We'll name it people, and then we'll call the reader.GetPeople
method. And then as we did above, we'll pass the people variable through to our View.
So let's run the application and then click on the Get People using an Abstract Type link. And what we see is the results are exactly the same as when we used the concrete type. So whether I use the concrete type or the abstract type, we get the same results. So then the next question is, why do I care about this? Well, the reason I care is that I do not have control over the library project. That's another team that works on it. So let's say that that other team says, you know what, we really don't want to use a Person array here, we would much rather use a List because of the other ways that we're using this method in other parts of the application.
I will need to make a little bit of a change to the code here so that this compiles on the library side, but we won't worry about these details right now. We'll actually see this code in the example in the next chapter. So the team that's managing the library has changed the type that's returning from this GetPeople method. What happens if we try to build our application? Well, we get a build failure at this point. And if we look at what that failure is, it says, Cannot implicitly convert type List to Person array.
So the GetPeople method is now returning a List, but we're trying to assign it to a variable that's of type Person array. There's a couple ways we can deal with this, I can say .ToArray, but we're more likely to change the type of our variable to a List. And now if we build our application, we get a successful build, and if we run the application we can see that our link for the Concrete Type works again.
But what's more important here is what we did not have to change. Our Abstract Type link still works, and we didn't have to modify any of that code. And that's because when we're programming against the abstraction, it doesn't care what the concrete type is. When we call the GetPeople method, I don't care what the collection type is, whether it's a list or an array, all I care about is that it has the IEnumerable functionality. If the List that's returned from this changes to something else, like a read only collection of Person, our code doesn't need to change. By programming against just the piece of functionality that we need, we future proof the code. One question I often get is couldn't we have just used var here and not worried about any of this? The answer is technically yes.
If we change our people variable to use var, then our code would work the same way, but there is a fundamental difference in how we approach the code. When we use var, what we're really saying is give me whatever type is coming back from this method, and in this case, if we hover over the var, we see it's a List where T is Person, so this people variable is a List. When we use IEnumerable, we're saying give me whatever type is coming back from this method, as long as it has the behavior that I need. If we do not specify the behavior that we need, then we may run into issues later on. We'll see examples of this when we look at explicit implementation and default interface implementation later in the book. Personally, I do use var in a lot of situations, and you will see it sprinkled through the code as we look at more examples, but when I care about a particular functionality, I'll be sure to specify the type or interface that I need. So far, we took a look at the what and why of interfaces. An interface is a contract. When a class implements an interface, it promises it will have a certain set of functionality in the form of properties, methods, events, and indexers. We looked at the differences when using a concrete class, an abstract class, and an interface. In our polygon example, we saw that a concrete class could give us runtime errors if a child class does not override the getArea method. With an abstract class or an interface, we are forced to provide an implementation of getArea. Otherwise, our code will not compile. We also saw how we could put common functionality into an abstract base class. In a later chapter, we will take a closer look at the differences and similarities of interfaces and abstract classes. Then we saw how interfaces can make our code more flexible. When we program with a concrete class, our code is brittle. When the underlying collection changes, our code breaks. When we program with an abstraction the interface, then we specify that we only care
about a particular set of functionality rather than any specific implementation. This allowed our code to accept different collection types without needing to be modified. In the next chapter, we will get into the how by creating and implementing our own interfaces, and this will make our applications easy to extend.
Chapter 3 How to Create Interfaces to Add Extensibility
We have seen how interfaces can help us future proof our code. Now it is time to look at how interfaces can make our code easy to extend. If we come up with a contract that the core application can use, then we can plug in any number of different implementations that adhere to that contract. For this, we will explore the how of interfaces. First, we will see how to create our own interface. In this case, a repository interface to help us connect to different sources of data. Then we will see how to implement this interface by coding up a number of repositories. These will let us get data from a web service, from a text file, and from a SQL database. After creating these different implementations, we will remove code duplication by using a factory method. This will move specific details to a different part of our project, and that way we can focus on the important functionality of the code that we're working on. In my own work, I've created a rules engine that allows different rule types to be easily added. I have also put together custom workflows that allow for full control over each step, and I have worked with an authorization client that could attach to different types of user stores. These extensible applications were made possible with interfaces. Let's say we have an application that will be deployed to multiple client sites. Each client has their own preference for storing and retrieving data. Our application needs to get data from a variety of sources. It may be a web service that gives us a representation of the data, where JSON stands for JavaScript Object Notation, or a CSV text file that contains values, or a relational database like Microsoft SQL Server or
SQLite. It may be a document database or a service in a cloud, such as Amazon AWS, or an Azure function call that gives us the data that we need. There's a specific way to interact with each of these data sources. When we use a web service, we make HTTP calls over a network. With a text file, we use file input/output operations. But we do not want our core application code to have to deal with these details. Instead, we will set up a repository. Here is a description of the repository pattern from Martin Fowler. The repository pattern mediates between the domain and data mapping layers using a interface for accessing domain objects. Let's explore what this means. The repository pattern lets us separate our application code from specific data storage technology. We do not want our application code to need to know how to make HTTP calls or crack open a file on the file system. Instead, the repository is responsible for those details. The repository then exposes the data as standard C# objects that our application can use. In our case, we will create several different repositories. A service repository will know how to make HTTP calls to a web service that returns JSON data, data that's formatted with JavaScript Object Notation. A CSV repository will know how to open a file on the file system and parse values. A SQL repository will know how to connect to a SQLite database using Entity Framework. And how do these plug in to our application? With an interface. We will create a repository interface, and this will be our contract that has the methods that interact with our data.
There are several different approaches to repositories. We will start out with a CRUD repository where CRUD stands for create, read, update, and delete. These are basic operations that we need to fetch and update data objects. A CRUD repository describes both read and write operations. But when we look at the code that uses the data, we only have read operations. There is a practice regarding interfaces to keep our code easier to maintain. That practice is for our interfaces to only contain methods that are used by the clients. So if we have client code that only reads data, then our interface should also only have read operations. So we will create a repository interface that only contains read operations.
We will use the term data reader instead of repository since it better describes the true functionality. Our interface has two members. GetPeople returns an IEnumerable of Person objects, and this allows us to get the entire collection of data. In addition, we have a GetPerson method that takes an id as a parameter and returns a single Person record. In the following demonstrations, I use Visual Studio 2019. Running the projects is a little bit different in Visual Studio Code. First, we will create a data reader that implements our interface. Specifically, we will create a service data reader. We will end up with multiple data readers. In addition to the service reader, we will have a CSV reader that uses a text file and a SQL reader that uses a SQLite database. Once the application is using these data readers, we will remove the duplicate code. Our interface will make this easier. So let's head to the code. So here we are in Visual Studio, and we have the Extensibility solution open. This has three projects. The PersonReader.interface project has our interface. The People.Service project is a web service that provides our data. And the PeopleViewer project is a web application that shows the data to our users. Before looking at the code, let's run the application to see what we have. When I press F5 to start the project, you'll see two console windows open. That's because our solution is set to start both the web service and our web application. And in our web application, we have three links, one to get data from a web service, one to get data from a CSV text file, and one to get data from a SQL database.
Right now, if we click on these, they do not provide any data. Before we leave, I want to take a quick look at the data that we'll be getting from the web service, and for this I'm going to navigate to the service location, and that is http://localhost:9874/people. And if we go to that location, we see that we get some data back. This data is formatted using JSON.
When we create our data reader, we will read this raw data from the service and convert the JSON data into C# objects that we can use in our application. So now let's look at the code. We'll start by going to the PersonReader.interface project and opening the IPersonReader.cs file.
This contains the interface that we will implement, and this has our two methods, GetPeople that returns an IEnumerable of Person and GetPerson that returns a single Person record based on an ID. Let's take a peek at the Person object. I'll just and say Peek Definition, and this shows us our Person class has six properties and an override of the ToString method. And these properties match up with fields that are coming back from our service.
So now let's create a service reader that will implement this interface. And for this sample, we'll put it in a separate project. I'm going to on the Readers folder and choose Add, New Project, and then when the dialog comes up. I'll choose a Class library, and we'll see this is a project for creating a class library that targets .NET Standard or .NET Core. And even though it's not in the description, this also targets .NET 5. We'll click Next, and for the name of our project we'll choose PersonReader.Service, and we'll click Next. And for the Target Framework, we'll choose .NET 5.0, and then we'll just create the project. Inside our new project, we'll see we have a Class1.cs file, and I will on this and choose Rename and call this ServiceReader. Now I like to rename the files because when I rename a file, Visual Studio will ask me if I also want to rename the class. And when I say yes, the Class1 that we have will change to ServiceReader.
Our ServiceReader needs to implement the IPersonReader interface, so I'll say : IPersonReader. And you'll notice I'm not getting any IntelliSense on this, and that's because I have not added a project reference to the interface project, and I also do not have a using statement at the top of my file. But Visual Studio helps us out here. If I press Ctrl+dot, one of the options is to add a reference to the PersonReader.Interface project, and it will also add the using statement at the top of our file.
If I build at this point, I will get a build failure, and that's because we're saying that ServiceReader implements the IPersonReader interface, but it does not provide implementations for the GetPeople method or the GetPerson method, so we're not fulfilling our contract. One way we could add these methods would be to go back to our interface and copy and paste them from our code. But Visual Studio gives us an easier way. If I click on the IPersonReader and press Ctrl+dot, Visual Studio gives me an option to implement the interface.
You'll also see there's another option to implement all members explicitly, and we'll talk about explicit implementation a little later in the book. For now, we'll choose Implement interface, and Visual Studio creates our GetPeople method and our GetPerson method for us. And if I use F6 to build again, at this point our build succeeds. We are technically fulfilling the contract since we have these two methods.
Our methods don't do anything useful at this point, but there's nothing in the contract that says our methods have to be useful. They just have to be implemented. So for this ServiceReader, we do need to make a web service call, and for this, I'm going to use a WebCclient. I'm not getting IntelliSense here, and that's because I need a using statement. Again, I'll press Ctrl+dot, and Visual Studio offers to add the using statement for me. A WebClient is an older technology. It's generally recommended that we use HTTP clients for making service calls, but the HTTP client has all asynchronous methods. For this sample, I did not want to use asynchronous code because I wanted to make sure that we could focus on the interfaces themselves.
Asynchronous programming is an extremely important topic in C#, and you'll definitely want to look into that. But for now, we'll skip asynchronous code so that we can focus on how interfaces work. Next, I'm going to add a field to hold the baseUri for a service, and this will be http://localhost:9874. In a real application, I would get this from configuration, but for our sample, will hard code this in. I'm going to add one more field for JsonSerializerOptions. And again, press Ctrl+dot to bring in a reference for that. And these options will help convert the JSON data into our C# objects. And the setting that I want here is PropertyNameCaseInsensitive, and we'll set that to true.
And the reason for this option is the JSON data coming back has lowercase property names and our C# object has uppercase names, so this will make sure that it can match those up. And now that we have this, we can implement our GetPeople method. I'll start by creating a more specific address, and this will be our baseUri that we have above, and then we'll say /people. So this will get us to the specific location for our GetPeople method. The WebClient has a DownloadString option, and to use this, we pass in the address. As you can imagine, this returns a string, so we'll create a variable to hold that string reply.
That reply is in a JSON format, so our final step is to deserialize that into C# objects. For this, we use the JsonSerializer, and we call
the Deserialize method. Deserialize has a generic type parameter, and this is the type that we want to convert the JSON data into. In this case, we'll say IEnumerable. And Deserialize has two parameters, one for the JSON string, which is our reply, and another optional one for our JsonSerializerOptions. So what comes back from this is an IEnumerable, and we can return that from our GetPeople method.
The GetPerson method is similar, so I'll start by copying and pasting the code, and then we can make a few modifications. For the web service address, we say people/{id}, and so we'll pass our id parameter through. And for our Deserialize method, instead of using IEnumerable, we'll just use Person. And if we build at this point, we should get a successful build. So our build succeeded, and we have an implementation of the IPersonReader interface. And again, that means our ServiceReader has both a GetPeople and a GetPerson method, and in this case it's using a WebClient to make a service call, and then we're turning that into C# objects that we can use. So let's use our new ServiceReader in our application. For this, I'm going to go to the PeopleViewer project, and before looking at code, I'll add a reference to our new DataReader. So I'll on Dependencies, choose Add Project Reference, and then we'll add a reference to PersonReader.Service, the project that we just created. The place we want to use this is in the PeopleController, and that's in the
Controllers folder of our application. So let's open PeopleController.cs to see what we have. In the PeopleController, we have three actions that correspond to the links on our web page. If we look at the UseService action, it currently has two lines of code. The first sets a title value in the ViewData. ViewData is a set of pairs, and we can use this value in the view and display it to the user. In this case, we're setting the title to Using a Web Service. The second line creates and returns the view. The first parameter is the name of the view, in this case, Index. The second parameter is the model that we're passing through. In our case, our model is our people data. So let's add some code to use our new ServiceReader. I'll start with a variable, which is of type IPersonReader called reader, and we'll set that to a new ServiceReader. And again, I'll press Ctrl+dot to bring in the using statement for that. Next, I want to call the GetPeople method on that data reader. From our interface, I know that this returns an IEnumerable, so we'll create a variable called people, and we'll set that to what comes back from our reader.GetPeople method. I'm also going to set one other piece of ViewData, and this will be the ReaderType. And this is a way that we can display the specific reader type on our UI to make sure we're using the right one.
For this, we'll say reader.GetType and then .ToString so that we can store it in our ViewData. One last piece is I need to pass the model through to our view, so instead of having an empty list, we'll pass our IEnumerable through. And this should get our application going.
I'll press F5 to run our application, and then I'll click on the link for Get People from Web Service, and here we can see our data coming back. At the top of our data, we can see the string Using a Web Service, and this is coming from our ViewData. And then down at the bottom we have a Reader Type, and this is also coming from our ViewData. And we can see the type as PersonReader.Service.ServiceReader. And this is the fully qualified type name of the ServiceReader that we created. So this gives us the first step. We created a reader that connects to a web service, and next we'll look at a CSV reader that reads from a text file and a SQL reader that connects to a database.
Chapter 4 How to Use CSV & SQL Data Readers
In addition to the ServiceReader, we want to have a reader that can read a CSV file and one that can talk to a SQL database. I have already created these readers, so we can add them to our application and see how to use the interface to consolidate our code. Let's start by adding the CSVReader. For this, I'll on the Readers folder and say Add, Existing Project, and then we'll navigate to our Solution folder, which is already open. Then we'll go to PersonReader.CSV and open the PersonReader.CSV.csproj file. So let's go to our PersonReader.CSV project and open the CSVReader class. And then let's collapse to definitions so we can see things a little easier. So here we can see that our CSVReader class implements the IPersonReader interface, and that means that it does have our two methods, GetPeople and GetPerson.
And if we look at the GetPeople method, it looks quite a bit different than what we had in our ServiceReader. It's actually loading
a file and then parsing the data. And if we look at the ParseData method, we can see it does quite a few things to turn a values file into C# objects that we can use in our code.
But the details aren't important for our application. Our application only cares that it implements the IPersonReader interface and has the GetPeople method. So let's go back to our people viewer project and use this. We'll go to our dependencies, and add a project reference to the PersonReader.CSV project that we just added, and then we'll go back to our PeopleController class. Our UseCSV method is going to be very similar to our UseService method. And I'll just need to make a couple updates. First, I need to change the ServiceReader to a CSVReader.
And again, I'll use Ctrl + dot to bring in a using statement for that. The other thing I need to do is pass the people variable through as the model for our view, and this is enough code to get data from a text file. Let's build and run our application to see what we get. If we click on the Get People from a CSV Text File link, we see that we're getting data back. And down at the bottom, we can see the reader type is different. It now says PersonReader.CSV.CSVReader.
I can also tell by looking at the data that our data is coming from somewhere else because our text file has an extra record. So now we have an application that can get data from a text file or from a web service. The third thing that we want to implement is getting data from a SQL database. So let's go back to our code and do that. The SQLReader is already created. So, again, we'll on the Readers folder,
say Add, Existing Project, and this time we'll navigate to the PersonReader.SQL folder and open the PersonReader.SQL.csproj file, and let's look to see what we have in here. We'll open up the SQLReader class, and again, I'll collapse to definitions. Our SQLReader does implement the IPersonReader interface so it has both the GetPeople and GetPerson method. And in this case, the implementation is different. It gets data from a SQL database using Entity Framework.
The SQL database it's getting it from is a SQLite database, so you don't need to worry about having a SQL Server running on your machine. Everything you need to get data from a SQLite database is included in this project. So now let's add this to our people viewer project. So again, we'll add a reference to the project, PersonReader.SQL, and then we'll go back to our PeopleController. And like I did with the CSVReader, we'll copy and paste some code, and then change the type from CSVReader to SQLReader, bring in
the using statement, and then pass the people variable through to our view. And let's write our application again.
When we click on our third link, now we're getting data from a SQL database, and looking at the reader type at the bottom, we can see we're using PersonReader.SQL.SQLReader. So now our application can get data from a SQL database, or from a CSV text file, or from a web service. But something I really don't like in our code is all of the code duplication that we have. When we look at our three actions, there's really two differences.
One is the type of reader that we new up, whether a ServiceReader, a CSVReader, or a SQLReader, and the other is a string that we're passing through as a title to our view. What I'd really like to do is consolidate some of this code. To consolidate the code, we'll refactor the UseSQL method. We'll take the things that are unique and move them to the top of the method. So our ViewData with the title will go at the very top, and then we have our IPersonReader variable where we're newing up the SQLReader specifically. These other three lines I'll extract to another method. So I'll highlight this and press Ctrl + dot, and Visual Studio offers to extract a method for us, and we'll call this PopulatePeople view, since that's what we're trying to do. All of the refactoring tools that I've been showing you are built into Visual Studio. I am not using any third party tools at this point. So everything you see are things that you can do yourself without having anything special installed. One interesting thing about refactoring this method is that the tool created a parameter for us, an IPersonReader, and that's because it recognized that we were calling the GetPeople method within the code. So now that we have this, let's share it with our other methods. So far, CSVReader, we'll move the title to the top, and then we'll replace these three lines with a call to our new PopulatePeopleView method. And we'll do the same thing with our serviceReader, move the title to the top, and then replace these three lines with a call to the PopulatePeopleView method.
So as a sanity check, let's rerun our application and make sure it still behaves the same way. So if I click on the web service, we see that we're using the ServiceReader. And if we choose the CSV text file, we see we're using the CSVReader. And if we use the SQL database, we'll see that we're using the SQLReader. So our code still works the same way. I'm a little bit happier because we've removed some duplication, but I'd kind of like to take this a step further. A and to prepare for that, I'm going to inline some of these variables. So I'll click on the reader variable in the UseService method, press Ctrl + dot, and one of our options is inline temporary variable. And what that will do is eliminate the reader variable and put the new ServiceReader call directly into the argument for the PopulatePeopleView method. And we'll do this for our other methods as well. This code does look a little strange, and generally, it's something that I would not have in my production code. I tend to have intermediate variables to enhance readability. But in our next steps, we'll be changing this quite a bit. One thing I like about the
code that we have now is the PopulatePeopleView method only cares about the interface. It needs an object that has the GetPeople method on it, but it does not care about the specific data reader, just that it implements that interface.
One thing I do not like about this code is that our UI is newing up the data readers.
This is outside the responsibility of our UI code, so we really should move this somewhere else. For that, we will use a factory method to create the appropriate data reader. Let's take a quick look at what a factory method is, and then we'll update the code. To move forward, we will use a factory method for the data reader. A factory method is responsible for getting us an instance of an object. In our case, the factory will return an object that implements our IPersonReader interface. So let's look at this code. The GetReader method returns an IPersonReader.
The UI code in the controller can call this method instead of taking responsibility for creating the objects itself. The factory method takes a string as a parameter. The string will indicate what type of reader we want. Based on the value of the parameter, we new up a ServiceReader, a CSVReader or a SQLReader. Now we will put this into action. We will add the factory method to create the readers and then use it in the UI code. This means that we can remove references to the specific readers from the UI code in the Controller class. The core application will only know about the interface. This keeps the UI code insulated from details that it should not be concerned with. This is the extensibility project that we were just working with. We'll put the factory method into a separate project. This way our PeopleViewer project can reference only the factory and does not need to know about any of the specific data readers or their projects. So let's on the Readers folder and choose Add, New Project, and we'll choose a class library like we did before, and we'll name this PersonReader.Factory. And as before, we'll choose a .NET 5.0 project. Next, I'll add references to our data readers, so I'll on Dependencies, choose Add Project Reference, and then we'll choose PersonReader.CSV, PersonReader.Service, and PersonReader.SQL, and we also need a reference to the interface, PersonReader.Interface. And then next, we'll rename our file from Class1 to ReaderFactory. So our ReaderFactory is a public class, and it will have a public method that returns an IPersonReader. And again, I'll control that to bring in a using statement, and we'll call this GetReader.
And this will take a string as a parameter, which we'll call readerType. There are several ways that we can implement the code. I am going to use a switch statement. Other options include switch expressions and also members. C# has a lot of options. I'm going to let Visual Studio help me out a little bit. If I type in switch, Visual Studio offers me a code snippet, and it says Tab twice to insert. So I do that, and it says, what would you like to switch on? In this case, I'll choose readerType. And when I hit Enter, it closes out the snippet. So let's fill in some cases. We'll start with Service. So if someone sends us Service as a parameter, we'll return a new ServiceReader, and I'll be using Ctrl+dot quite a bit to bring in using statements. If someone asked for a CSV reader, then we'll return a new CSVReader. And if someone asks for a SQL reader, we'll return a new SQLReader. If we hit the default case, something went wrong, so we'll throw a new ArgumentException. And the message that we'll pass back is Invalid reader type, and then we'll pass in the readerType that we got as an argument.
So here we have a factory method, and based on the parameter that's passed in, it will give us back a class that implements the IPersonReader interface. And our current options are a ServiceReader, a CSVReader or a SQLReader, but we can easily expand this. So let's go back to our PeopleViewer application and use this. In the PeopleViewer project, I'll add a reference to the project that we just created, PersonReader.Factory. And then we'll go back to the Controllers folder and open up our PeopleController. At the top of my PeopleController class, I'll add a private field for our new ReaderFactory. And then we'll use this in our PopulatePeopleView method. So instead of having the IPersonReader come in as an argument, we will ask the reader factory to provide it for us. So we'll call factory.GetReader and assign that to an IPersonReader variable. We do need to pass an argument to GetReader that specifies the reader type. So let's add a parameter to the PopulatePeopleView method, and we'll call that readerType. And since we changed the
method signature in our PopulatePeopleView, we'll need to update all of our other methods as well. So in our UseService method, we pass in the string Service. For UseCSV, we'll pass in CSV, and for UseSQL, we'll pass in SQL. So now that we've updated our code, let's run and make sure everything still works.
If we click on our Service link, we see that our type is PersonReader.Service.ServiceReader. If we go home and click on the CSV, we see that we're getting data from a CSV reader. And if we go back and click on our SQL link, we see that we get data from the SQL reader. So our application still behaves the same way, but we've done something very interesting to our code. If we go back to our PeopleController class, which specifies the UI functionality for our application, we find that several of the using statements are grayed
out. The references to the project for the CSV reader, the Service reader, and the SQL reader are not being used. That means we can get rid of them. And as a shortcut, I'll use Ctrl+dot, and you'll see there's an option to remove unnecessary usings. And what that leaves us with is that our PeopleController is only referencing the interface and the factory. It doesn't care about any of the specific data readers.
And we can go a step further in our project. I can go to the project references and remove links to the CSV, Service, and SQL projects. This means that our PeopleViewer project does not have any direct knowledge of any specific data readers. And if we run the application again, we'll see that it still works. We can still get data from our web service, from our text file or from our SQL database. We only care about the interface in this project. We have the piece of functionality that we need, the ability to call the GetPeople method. We do not care what object we have as long as it has a GetPeople method. This gives us a better separation of concerns and puts responsibilities where they belong. It does not make a whole lot of sense for one application to reference three different data sources like we have here. Most likely our application will need access to one data source based on our client's needs, and we'll look at that in a future demonstration. By using our data reader interface, we are able to remove references to any specific data readers in our calling code.
The PopulatePeopleView method references IPersonReader, and it asks the factory method to provide an object that implements that. When we call the GetPeople method, we know that we will get an IEnumerable back since that is what is specified by the interface. We do not care about the details of how that happens. We just care that it does happen. The details can be someone else's responsibility. This also means that if we need to switch to a different SQL technology, for example, one that uses Oracle or a MySQL database, we just need to update the factory method to create the right data reader. Our calling code does not need to care about that. I have had to make a change like this from a Microsoft SQL Server database to an Oracle database, and I will talk about that a bit more in the next chapter. In this chapter, we have focused on the how of interfaces. Specifically, we saw how to create a repository interface. We simplified the CRUD operations down to just the read operations that we needed, and the interface represents how we interact with the data. We saw how to implement the interface. We created a service reader and saw how Visual Studio gives us an easy way to stub out all the members of an interface. In addition to the service reader, we use data readers to access a text file and a SQL database. With the
interface in place, it was easy to eliminate code duplication. The interface became a parameter that we could use to create a consolidated method. Then we took that one step further to create a factory method. This moved the details out of our main application so that we could focus on the important functionality, getting a collection of person objects that we can display for our users. As mentioned, we probably only want to access a single data source from our application, but we may need to change that data source. In the next chapter, we will look at how interfaces can help us choose the functionality our application will use at runtime.
Chapter 5 Dynamic Loading and Unit Testing
We have created several different data readers to implement our interface. Whenever we use them, we have compile time references to the concrete classes, but we can take this a step further. We can update our application so that it does not know anything about the specific data reader classes at compile time. Instead, we dynamically load a data reader when the application runs. This removes the connection between our application and any specific data reader implementation, and this also makes our code easier to unit test. This time, we will look at more of the how and the why of interfaces. We will do this by looking at some scenarios. The first is to change a data reader of an internal application without needing to recompile or redeploy. The second scenario puts our application into the hands of multiple clients, and each one has their own way to store data. By using the interface only, we can focus on the important functionality and not worry about the details. We will do this by updating our code to make decisions when our application runs rather than when it is compiled. This means that we can change the behavior of the application without recompiling. Since we do not have to recompile to make these changes, our application is easier to maintain and deploy, and it also makes unit testing easier. Unit testing has been a huge benefit in my development work. We will see how interfaces can make our unit tests easy to read, write, and understand. So let's keep going to see how we can get all of these benefits. The first scenario is one I came across while working
as a corporate developer. One of my applications connected to a database to get some data. Initially, the database ran on Microsoft SQL Server, but then the system was going through an update, which included moving the database to an Oracle server, and the new database also had a different layout of tables and fields. One way I could have made the transition is to replace the Microsoft SQL Server code with Oracle code. The thing I did not like about this approach is that I would need to schedule the deployment of my application to match the application deployment, so instead, I took a different approach. I added some configuration to the application to specify whether the application should use the Microsoft SQL database or the Oracle database. During testing, I could change the configuration back and forth, either to connect to the test Microsoft SQL database or to the test Oracle database. This let me make sure that the functionality of my application was the same in either environment. Once my updated code was ready, I went through our deployment process to get the application onto our production servers, and I had the configuration pointed at the Microsoft SQL Server, database. Then during the deployment of the system, all I had to do was change the configuration to point to the new Oracle database. This greatly simplified the deployment for this application, and fortunately, things went smoothly. For the next sample, we will do something similar with our example application. We will add code that lets us pick which data reader to use based on configuration. By setting the configuration, we can use the service data reader, the CSV data reader, or the SQL data reader, and we can do this by changing configuration. We do not need to recompile or redeploy the application, and as we'll see, the data reader interface plays a key role in making this possible. In the following demonstrations, I used Visual Studio 2019. Running the projects is a little bit different in Visual Studio Code. Now let's go to the code. In
this sample, we will update our application to look in Configuration to decide which data reader to use, in the code, we will update the controller to use the new Configuration, and we will update the user interface, the view, to add a new link to our screen. This will give us the ability to switch to a different data reader without recompiling our application. We only need to update the Configuration to use a new data reader. Here we are back in Visual Studio with the extensibility solution open. This code is in the same state that we left it in the previous demonstration, three links that use three different data readers to get data.
We will change this so the data reader is selected based on what is in a Configuration file. This will let us change the behavior of our application without recompiling it, so let's start by adding Configuration. We do need to know what the valid values are, and for that, we can look in our factory method. So let's go to the PersonReader.Factory project and open the ReaderFactory. This shows us our three current valid values, Service, CSV, and SQL. So let's go
to our PeopleViewer project and add a setting for this. So we'll open our PeopleViewer project and go to the appsettings.json file. For our Configuration, we'll add a new key and value. For the key, I'll use PersonReader type, and for the value, we'll use Service to start. You might notice we have green squiggles on our appsettings.json, and it tells us there's a problem with the schema. This is actually a known bug with Visual Studio.
And if we save and close the file, the green squiggles go away. So now, let's use this Configuration in our code. We're going to use this setting in the PeopleController class. So on our PeopleViewer project, we'll go to the Controllers folder and open the PeopleController.cs file. Reading from Configuration is something that's built in, so I can reference some classes that already exist. In the PeopleController class, I'll create a private field to hold our Configuration, and this will be of type IConfiguration, and we'll use Ctrl+dot to bring in the
using statement, and we'll call this Configuration. I'm going to populate this in a constructor.
I like code snippets, so I'll type ctor, which is a code snippet for a constructor, and when I hit Tab twice, Visual Studio stubs that out for me. For this constructor, I'll add a parameter, which is an IConfiguration, and we'll call this Configuration. And in the body of the constructor, we'll set our private field to what's coming in. One thing to note is the IConfiguration argument is filled in for us automatically by the dependency injection container that is built into a ASP.NET MVC, and we will see more specifically how this works in a later demonstration. I'm going to create a new action that holds our functionality, so we'll create a new method that returns an IActionResult, and we'll call this UseConfiguredReader.
And in here, I'll get the readerType setting out of Configuration, and for this, we can use our private Configuration field and tell it we want the PersonReaderType setting. With the current setting, this will give us the string service. Next, I want to set the Title and the ViewData, and I'll copy a line of code from another method to make this a little easier, and we'll set this string to Using Configured Reader, and the last step is to call our existing PopulatePeopleView method and pass in our readerType as a parameter. And now that we have our new Use Configured Reader action, we need to add a link on our screen to use this, and for that, we'll go to the Views, so I'll open up the Views folder and click on the Home folder and then Index.cshtml. Now, it's important that we go to the Home folder because the Index.cshtml in that folder is the one that displays our links, so let's add a new link. I'll start by giving it its own section, an unordered list, and then I'll copy one of our existing links that we already have. In the ActionLink method, the first parameter is what's displayed on the screen, so we'll say, Get people using Reader from Configuration; the second parameter is the action method that we want to call. In this case, we'll say, UseConfiguredReader, and that matches up with the new action that we just created; the third parameter is the controller that we want to use, in this case, we're
using the PeopleController. So with our new link in place, let's run our application. And if I click on the link to use a Reader from Configuration, we can see that our data is coming from the ServiceReader.
That's not much different than what we had before, but let's look at something interesting. I'm going to go back to our project, on the project itself, and say Open Folder in File Explorer. This puts us into the project folder for our code, but I want to navigate to the output folder. So we'll go to bin, Debug, and net.0, and in this folder, we have an appsettings.json file, and I'm going to open this using Notepad. And then for the setting, I'm going to change the Service to a CSV and save the file. And now, I'm going to go back to my folder and on the PeopleViewer.exe file, and this will start our application. Since I'm running this outside of the debugger, I'll have to navigate to this location myself. One easy way of doing this is to highlight the value and on it, and that will put it on your clipboard. Then we can use the Windows key with R for run and then paste that value in, and when we say OK, it will open our browser to that
location. Now when I click on the link for the Reader from Configuration, we get data from our CSV file. So by using Configuration, we can decide which data reader to use without recompiling the application.
Let's close this, as well as the application and change it to the SQL reader, and we'll save that file, on PeopleViewer.exe, and then navigate to that localhost location again. And now if we click on the Reader from Configuration, we get data from our SQL database. This worked really well for me in the scenario I described.
My application had data readers for both the old Microsoft SQL database and the new Oracle database. I could use the Configuration file to switch back and forth to make sure everything worked in the test environment. Once tested, I can deploy my application. Then on the day that the database officially switched over, all I had to do was update the Configuration. I did not need to coordinate the build and deployment because my code was already built, tested, and deployed. The second scenario is one I came across while working for a startup, and this is similar to the scenario that I described earlier for our sample application. In this case, we have an application that is deployed for multiple clients. Each client has their own data storage technology. We can use configuration to pick a data reader as we just saw, but there is another downside to the current approach, the entire application is compiled together. This means that if we need to change the code in a data reader, we need to recompile the entire
application. If we need to add a new data reader, we need to recompile the application. As we will see, this can lead to support issues, but there is a way that we can solve this issue, dynamic loading. We have already seen our advice to program to an abstraction rather than a concrete type. In our world, this means program to an interface rather than a concrete class. We have done this to a certain extent already. In our application code, we use a factory method to get an object that implements our interface, IPersonReader.
Then we call the GetPeople method that is included in that interface. Our application code has no references to any of the concrete data readers, whether the service reader, CSV reader or SQL reader. Our factory method has the responsibility of giving us an instance of a data reader, and it does have compile time references to each of our data readers. We can remove these compile time references, but do we really need to do this?
If we bring on a new client and that client uses a different data storage technology, then we need to recompile the entire application. If an existing client decides to change how they store data, then we need to recompile the entire application. Another side effect of this is that each client application has code for all of the data readers, even though they only need one. And then comes the support issues. If we constantly need to recompile our application for clients, that means different clients will potentially have different versions of the application, each with its own set of bugs that need to be fixed. This makes support very difficult, particularly when we have a large number of clients. Our dynamic reader factory will not have any compile time references to the data readers. Instead, it will load up the appropriate data reader assemblies from the filesystem at runtime. This means that we can compile our core application separately from any data readers. It also means that each data reader can be compiled separately. This way we can give each client just the one data reader that they need. One major effect is that each client can have the same build of the core application, and that makes it much easier to support. Our dynamic factory method uses reflection
to load up the appropriate data reader assembly at runtime. Here are the basic steps. First, we check configuration to see what type of data reader we need to load and also the assembly where we can find it. Next, we load the assembly from the filesystem. Then we look for the specific data reader type inside that assembly. With that information, we can create an instance of the data reader that we then return from the factory. Don't worry if you don't understand the details of how this code works. The main reason for showing this code is so that you can have a good idea of what is possible when we use interfaces. I have found this technique to be very useful, but it did take me a while to fully grasp the details.
Chapter 6 Dynamic Reader Factory
In this sample, we will review the dynamic loading code that has already been added to the application. With this in place, our application will have no compile time references to any specific data reader. This means that we can change the data reader that the application uses without recompiling the application. This will allow us to easily support multiple clients with different data sources without having a deployment and support nightmare. This time, we have the dynamic loading solution open in Visual Studio. This application is a little different from the one that we've been working with. Let's run it and then look through the code. In this case, we just have one link to use a runtime reader. And at this point, our application is set to use the service reader. So let's go back and look at our projects. In our Readers folder, we only have projects for our interface and our factory. We do not have any specific data readers here. We do have a ReadersForCompile folder that has all of our data readers. And even though these are included in the solution, none of our other projects reference them directly.
These are here so that the appropriate files can be automatically copied to the output folder of our application. This was the easiest way to set this up so that you can experiment with the code yourself without having to do too many steps. So let's go to the PeopleViewer project, open up the Controllers folder, and look at our PeopleController. Similar to our last example, the PeopleController constructor takes an IConfiguration as a parameter. But unlike our previous example, instead of using the configuration directly, it passes it to the constructor of the ReaderFactory. We'll see how this gets used by the ReaderFactory a little bit later. If we look at our UseRuntimeReader action, we call the GetReader method on the readerFactory, but notice the GetReader method does not take any parameters. The rest of our code looks the same as we've seen before. Most of the interesting code happens in the ReaderFactory, so let's go to the PersonReader.Factory project and open the
ReaderFactory file. Now we won't go through all of the details of this code. That's outside the scope of this book. We will do an overview of what each section does.
In the constructor for our ReaderFactory, we get a reference to configuration that we store in a private field, and then we use that in the GetReader method. In the Check configuration section, we get the configuration value for DataReader:ReaderAssembly. Let's go to our appsettings file to see what that looks like, so we'll open appsettings.json in our PeopleViewer project.
Here we have a DataReader configuration item that has two values, one for the ReaderAssembly and one for the ReaderType. The ReaderAssembly is the name of the DLL that's on the filesystem, in this case, PersonReader.Service.dll. The ReaderType is the type name for the DataReader. Right now, this is set to PersonReader.Service.ServiceReader. So going back to our factory method, we get the name of the assembly from Configuration and then set the location, and we'll see what that location looks like on the filesystem when we run the application. The next step is to load the assembly. This loads the file from the filesystem and makes the assembly available to our application. The next step is to look for the type. This uses reflection to look inside the loaded assembly and try to find the one we're looking for, in this case, something called PersonReader.Service.ServiceReader.
Once we have the type, we can create the reader. This uses the Activator class to create an instance of our data reader. And the last step is to return our reader. So let's build and run our application again, and then we'll take a closer look at the files. With the way our application is currently configured, if we click on the Runtime Reader link, we get data using the ServiceReader. But now let's go to the filesystem to take a closer look at how this works. For this, I'm going to on the PeopleViewer project and choose Open Folder in File Explorer. Then we'll go to the output folder, bin, Debug, net5.0. The first thing I want to point out is the ReaderAssemblies folder. This contains all of the DLLs necessary for the data readers. If we look, we can see files for PersonReader.CSV.dll, PersonReader.Service.dll, and PersonReader.SQL.dll. All of the other files are supporting assemblies.
For example, the SQL data reader needs files for Entity Framework. These files are automatically copied to this output folder with a build event. So let's go back to our output folder and change our configuration. I'll open the appsettings.json file using Notepad, and we'll change the values from PersonReader.Service.dll to PersonReader.CSV.dll, and we'll do the same for the type name as well. And here I can save the configuration file, and on PeopleViewer.exe to start this up. And like we did with our last example, we'll navigate to localhost:5000. And when we click on our link, now we're getting data using the CSVReader, and we can do exactly the same thing for our SQL reader.
We'll change configuration, save the file, and restart our application. And now when we click on the link, we get data from a SQL database. There is a lot of code here that is confusing if you are not familiar with reflection, so don't worry if you do not understand the specifics. This is mainly to show something interesting that is possible when we use interfaces and dynamic loading together. Here's what this means for our clients. When we deploy the application to a client, they get the core application assemblies along with the configuration and assemblies for their particular data reader. If a client wants to change to a different data source, we just need to give them the new data reader assemblies and update the configuration. There is no need to rebuild or redeploy the core application. This also means that our core application does not need to go through our build, test, package, and deploy system, just the data reader needs to go through this process. For our scenario, this
simplifies support since all of our clients can be on the same version of the core application. There are drawbacks to this approach. This application is a bit brittle. If the configuration is incorrect or the data reader files are not in the right location, we get runtime errors. This makes things more difficult to debug. But in the instances where I have needed this, the benefits greatly outweigh the drawbacks, but it does go to show that we need to understand our tools so that we can use them effectively in our own environment. Unit testing is a big part of my development process. I have seen a number of benefits, and the result is that I am now a faster developer. There are different kinds of tests. Today, we're specifically dealing with unit tests where we test pieces of functionality in isolation. The isolation is a big part of that definition. When we are testing a small piece of functionality, we want to fake up anything that is not directly related to the test. In our case, it means creating a fake data reader that can give us records for testing. The good news is that interfaces can help us isolate code for easier unit testing. An interface is a plugin point to our code. We can unplug our real functionality and plug in a test object. Here is the code that I would like to test, the action in our PeopleController class. We want to make sure that it gets data from the data reader and provides it to the view as expected, but we do have some problems with trying to test this code. Right now, the controller is responsible for getting the data reader using the readerFactory. What I'd like to do is move that responsibility somewhere else. And for this, we will use dependency injection. Dependency injection is a set of software design principles and patterns that enable us to develop code. Loose coupling gives us a number of advantages such as making applications easier to maintain, extend, and test. Does that sound familiar? That's because loose coupling is also an advantage of using interfaces. I like to think of dependency injection as the fine art of
making things someone else's problem. In our case, I do not want to deal with the reader factory in the controller itself.
So, let's make it someone else's problem. To do this, we will update the controller. We will create a constructor that takes a dataReader as a parameter. It is up to whoever creates this controller to provide us with a data reader that we can use. The constructor will set a private field that holds the dataReader. This way our code can continue to use the data reader exactly as it did before by calling the GetPeople method. This removes all references of the reader factory from the controller. That is now someone else's problem. How does the data reader get into the constructor arguments? Through the dependency injection container that we have in ASP.NET MVC. We will need a little bit of code to set up the container, and we will see that in the demonstration. When the controller is created, the constructor argument will be automatically provided.
This moves the responsibility away from the controller, and it also makes the controller easier to unit test. Once we move the reader factory out of the controller, it becomes much easier to test. In our unit tests, we can create a fake data reader that has specific data for testing, and then we pass it to the constructor of the controller inside our tests.
This means that our unit tests do not need to worry about runtime configuration, separate data reader assemblies or reflection. We will look at some code to see how interfaces help us with dependency injection and unit testing. First, we will add a constructor to our controller that takes a data reader as a parameter. Then we will configure the dependency injection container so that it automatically supplies the data reader to the controller. Then we are ready to unit test the controller. To do this, we will use a fake data reader to provide us with some test data. This means that we do not have to depend on any of our real data readers. When we have the fake data reader, we can unit test the controller functionality with just a few lines of code. Here we are back in Visual Studio, and this time we have the unit testing solution open. This is similar to the dynamic loading solution from our last example, but this has a new PeopleViewer.Test project, and we'll be looking at that a bit later. But before we can unit test our controller functionality, we first need to inject the data reader into the controller, and this is a form of dependency injection. So let's go to our PeopleViewer project and open the Controllers folder and go to our PeopleController class.
We will no longer use the ReaderFactory to get the data reader ourselves. Instead, we will rely on someone else to provide us with a data reader that's ready to use. As we will see, the dependency injection container can handle most of these details for us. The first thing I'll do is remove references to the ReaderFactory. So we'll remove the private field, the body of the constructor, and also the line where we call the GetReader method. Now we do still need a data reader, so I'll add a private field for that, IPersonReader which will call reader. And now we'll update our constructor to require that. So we'll create a parameter for an IPersonReader, and we'll call this dataReader.
And then inside the body of the constructor, we can set our private field. Now our PeopleController class expects that it will be provided with a data reader. The data reader is provided by passing it as an argument to the constructor, and this is known as constructor injection. The next step is to add the data reader to the dependency injection container at startup. Then it will automatically get passed to the constructor. To configure the dependency injection container, we'll go to the startup file of our project and then look at the ConfigureServices method. So let's add our ReaderFactory. For this, we'll say services.AddSingleton, and we'll use the generic type parameter ReaderFactory. And don't forget to bring in a using statement if it's not already there. So what this does is it adds a reference to the ReaderFactory in our dependency injection container. So if our code needs a ReaderFactory, the dependency injection container will take care of newing that up for us. AddSingleton means that we only want to create the ReaderFactory one time. If
the application needs to use it more than once, the container will provide the same instance each time. To get the data reader that we need, we'll need to call the GetReader method on that factory, so let's add some more configuration. So we'll say services.AddSingleton. And this time, we'll use IPersonReader as our type. And again, we'll bring in a using statement for that. When we have an interface, which is an abstraction, we need to tell our dependency injection container how to get a concrete type for that. And as mentioned, we want to get it from the ReaderFactory. Now I'm going to add a lambda expression here, and don't be scared if this syntax looks a little scary. The first thing I'll do is use the GetService method to get a reference to our ReaderFactory. And once I have a reference to the ReaderFactory, we can call the GetReader method on that. And the GetReader method will provide the IPersonReader that we need for our application.
You might notice I have some green squiggles here, and that's because I have nullable reference types enabled for this project. With this setting turned on, we get warnings if something might possibly be null. In this case, I know it will not be null because I've added the reference to the ReaderFactory. And to let the compiler know I'm confident about this, I can add an exclamation point before the dot, and our warning will go away. So let's review this. In this code, we create a mapping between IPersonReader, our abstract type, and a concrete type. In this case, we tell the dependency injection container
that it should call the GetReader method on the ReaderFactory, and that will supply us an instance of a class that implements the IPersonReader interface. So in effect, this is saying if someone asks for an IPersonReader, I would like you to give them whatever comes back from the GetReader method of the ReaderFactory. And that is all the code that we need to inject the data reader into the controller. When the controller is created, the dependency injection container automatically provides us with the correct data reader. And if we run our application now, we'll see that we have the same behavior that we had before. When we click on the Runtime Reader link, it'll use the reader based on configuration.
And in this case, it's a ServiceReader. The good news is that we can test our controller pretty easily now. In our tests, we do not have to worry about the configuration and files required by the ReaderFactory. We can directly insert our own IPersonReader when we create the controller in the tests, and that is what we'll do next.
Chapter 7 Unit Testing
Our controller is not doing a whole lot, but there are a couple of things that we can test. First, we expect that the model of the view will be populated. This is the people object that we pass in on the last line of our code. Next, we can check the ReaderType value that gets set in the ViewData. In the case of our tests, it should be set to the type of our fake data reader.
We already have a test project set up, PeopleViewer.Test. This project was created using the unit test project in Visual Studio, and it uses the MSTest testing framework. In addition, I've already added references to the projects that we need, the PeopleViewer project,
which has the controller we want to test, as well as the PersonReader.Interface project that has the interface we need to use for our data. If we go to the PeopleController.Tests file, we have a couple of unit tests already stubbed out. This gives us a place to start. But before we add the tests, let's look at the fake data reader that we can use for our testing. This is in our FakeReader.cs file. Let's collapse the definitions and see what we have. As with our other data readers, the fake data reader implements the IPersonReader interface, and it has our two methods, GetPeople and GetPerson. And the GetPeople method provides us with some values that we can use for testing. Here we have two person records.
So let's go back to our PeopleControllerTests class and look at the tests that we have here. I like the name of my tests to tell what they're doing. For the first test, we have PeopleController, which is the object that we're testing, OnRuntimeReaderAction, which is the action that we're performing, and ModelIsPopulated, what we expect the final output will be. And for unit tests, I like to use the Arrange, Act, Assert layout. The Arrange section contains anything we need to set up our tests. The Act section has the action we're performing,
and the Assert section has our assertions. In our Arrange section, we'll have our setup, and we'll start with our fake data reader. So we'll say IPersonReader reader = new FakeReader, and then I want to create an instance of the controller that we test. So we'll say var controller = new PeopleController, and then we'll pass in our fake data reader as a parameter. In our Act section, we'll call the UseRuntimeReader method. So I'll say var result = controller.UseRuntimeReader, and then I'm going to hover over the var to see what comes back. Notice this says it's an interface IActionResult. And if we go back to our PeopleController file, we see that that's the type returning from our method.
But in order to check the model, we need a more specific type. If we look at our last line of code, we see that this view method returns a ViewResult, and that's a concrete type that implements the IActionResult that comes back from this method. In the unit test, I'm going to use this more specific type, and so I'm going to cast what's coming back from UseRuntimeReader as a ViewResult. And now if I hover over the var, we can see that this is a ViewResult.
Before going on, I'm going to do a little bit of a sanity check to make sure that the result is populated. So I'll say Assert.IsNotNull, and then we'll check this result variable. And if it is null for some reason, we'll add a message that says "Controller action failed". And this really means there's something that went wrong outside of what we're specifically testing for. So now that we have this result, let's get the model out of it. So I'll create a variable for the model, and we'll set it to result.model, and then I'll cast this to an IEnumerable of Person since I know that's the type that we're specifically dealing with.
You'll notice some greens squigglies here, and that's because it's warning us that result may be null. Well, the assertion that we have on the line before this ensures that it is not null, so I can add the exclamation point to say, I know this is not null.
So now that we have our model, let's make sure that it's populated and has the things that we expect. So we'll check to make sure that it's not null, and if it is, we'll say 'Model property is not populated", and then we'll make sure that there are two items in it. So we'll Assert.AreEqual, the number 2, our expected value, and model.Count, which will tell us how many items are in our model. And again, I have a warning that model may be null. And since I just checked it, I'll say I know it's not null. So let's save this and run our tests. And for this, we'll go to the Test Explorer. If you don't already have it open, you can go to the Test menu and then choose Test Explorer, and it will open the window for you. I'll click on the Run All button, and this will recompile our project and run our unit tests.
And that shows us that we have one test that passed, the one we just did, and one that's been skipped. And the reason it's been skipped is because we have an Assert.Inconclusive inside of our test.
So let's look at our second test, PeopleController_OnRuntimeReaderAction_ReaderTypeIsPopulated. So in this case, I'm going to go through the same process, but I'm going to expect that the reader type is populated with the value for our fake reader. To get started, I'll copy some code from our previous test and then make some modifications. In this case, I'll take the entire Arrange section and the first two lines of our Act section. We'll copy those to the clipboard and then put them into our test.
And as a reminder, the Arrange section creates a fake data reader and passes it to our PeopleController constructor. And our Act section calls our UseRuntimeReader method, casts it to a ViewResult, and makes sure that the result is not null. In the Arrange section, I'm going to add one more line, and this is a variable for our expected value, and I'll name this expectedType, and we'll set it to PeopleViewer.Test.FakeReader, and that's the type name for our FakeReader class. Now in our Act section, I want to get the actual type from our controller. So we'll say var actualType, and we'll set that equal to result.ViewData, and then we'll pass in our ReaderType string. And as a reminder, if we go back to our PeopleController class, this is the value that we're looking for. As with our other tests, I do need to tell this result is not null, and now we can do our assertions. First, we can check to make sure that it's not null. And if it is, we'll have the error message, ReaderType is not populated.
And for our second assertion, we'll make sure that our values are equal. So this compares the expectedType to the actualType. So let's go back to our Test Explorer and rerun our tests. And now we see both of them pass. What I like about these tests is that they're both fairly small and easy to read and write. When we have unit tests that are easy to read and write, we are more likely to use them. And interfaces are a great tool to help us get there. In summary we saw a couple of scenarios where interfaces are very useful, first, by changing a data reader in an internal application using configuration and then by creating a dynamic factory that makes it easier to support multiple clients with different data storage needs. We are relying on interfaces to supply the important functionality. In our case, it is getting people from our data source. But in this process, we remove the details of interacting with any particular data source. Our application can work with any data reader as long as it implements our interface. We also saw how we can change the behavior of our application without recompiling. Based on configuration, we can pull a data reader out of an assembly on the filesystem. This works great for our scenario of having multiple clients because we can change the data reader without recompiling
the application. This gives us easier maintenance because we can keep the bulk of our application unchanged. And if a client switches to a different data source, we do not have to redeploy the entire application, just the parts that are relevant to the new data source. And we saw how interfaces can make unit testing easier. They allow us to isolate our code from a real data reader. Instead, we can supply our own fake data reader, and that provides us with consistent test data. Next up, we will look at what it means to explicitly implement an interface. This is a rather obscure feature, but we will see that the concepts help us when we look at default interface implementation in the last chapter.
Chapter 8 Implementing Explicit Interfaces
Explicit interface implementation is a topic that I get asked about quite a bit, usually because of the Visual Studio popup that offers to implement all members explicitly. You will probably not come across explicit implementation very often, but there is at least one use case where we must use it, and it also helps us understand another feature of interfaces that we will see in the next chapter. We will take a look at what explicit implementation is and why we might need to use it. We will see that explicit implementation limits how interface members are used by forcing us to reference the interface. We will also see how explicit implementation resolves the problem of implementing interfaces with conflicting methods, such as two methods with the same name, but different return types. This is particularly useful when we have interface inheritance. We will see this by looking at the IEnumerable and IEnumerable interfaces. We will build our own implementation of IEnumerable and see how explicit implementation is necessary to resolve conflicting methods. And this is also preparation for default interface implementation. The restrictions on explicitly implemented members also apply to members with default implementation. Before looking at explicit implementation, let's review standard interface implementation. In this case, we have an ISaveable interface with one method, Save, and we have a Catalog class that implements the ISaveable interface.
When we create a variable of type Catalog and call Save, we get the expected output. If we create a variable with the interface type ISaveable, we get the same output. Regardless of the type of variable, we get the same results when we call the Save method.
Explicit implementation changes things a bit. When we provide an explicit implementation, that implementation belongs to the interface rather than the class, this means that we can only access the explicitly implemented member by using the interface type. If we try to use the class type, we will get an error. Another difference is that explicitly implemented members have no access modifiers. If we try to mark an explicit implementation as public or private, we will get a compiler error. Here is an example of an explicitly implemented interface member.
We have the same ISaveable interface that we saw earlier, but the implementation in the Catalog class is different. The main difference is that the implementation uses the interface name. So here we have ISaveable.Save, this indicates that the implementation belongs to the ISaveable interface rather than the Catalog class. Also, this method does not have an access modifier. If we try to mark this as public or
private, we will get a compiler error. So what is the effect of using explicit implementation? Our class behaves differently. If we create a variable of type catalog and try to call the Save method, we get a compiler error. This is because this Save method explicitly belongs to the interface, it is only accessible when we use the interface type. If we create a variable of type ISaveable, then we can call the save method and it will work. In addition, if we take our variable that is a catalog type and cast it to ISaveable, then save will also work.
The key here is that if we have an explicit implementation, we must reference the interface to call that member. In effect, the explicitly implemented member is hidden from the class. If we use IntelliSense in Visual Studio to inspect the members of this class variable, we will not see the explicit one. This forces the developer to use the interface if they want to access that functionality. In the following demonstrations, I use Visual Studio 2019. Running the projects is a little bit different in Visual Studio Code. Let's take a quick look at some code in Visual Studio. The interactive environment will help us
better understand what is happening. We will explicitly implement the ISaveable interface that we have just seen, and we will use that implementation to better understand how and where we can call the explicitly implemented member. Here, we have the explicit implementation solution open in Visual Studio. For this sample, we will look at the catalog saver project and this is a console application.
So let's open the library.cs to see what we have. And here we have the ISaveable interface with a single Save method and we have a Catalog class. So let's update our Catalog class so it implements the ISaveable interface, and I'll let Visual Studio help here. I'll press Ctrl+dot, and instead of choosing Implement interface, I'll choose Implement all members explicitly and this gives us an explicit implementation of the save method.
Notice that the method name is ISaveable.Save. And let's add some code to write to the console. So we'll just say Console.WriteLine and then we'll say Save. One thing to notice is there is no access modifier on the save method, and in fact, if we try to add one, such as public, and if we look at what we have on these red squigglies, it says The modifier public is not valid for this item. So when we use explicit implementation, we're not allowed to put access modifiers on the members. So now let's go to the program.cs file so we can see how the interface and the class behaves. I'll start by creating a Catalog variable called catalog and we'll set that to a new Catalog.
When I say catalog., notice that we do not see the save member here. And if I type in Save manually, we'll get an error that says that this is not part of the catalog type. So this shows us that the explicit implemented member is not available to the class itself. Let's try it with the interface variable ISaveable and we'll set this to a new catalog as well. And if we say saveable., notice that save does come up. So even though we're newing up the same type catalog, we can only reach the save method if we use the interface type for the variable.
We can use casting as well, so I can use the catalog variable that I have above and cast it to an ISaveable. And if we do this and say dot, notice that the save method is available to us. And again, the only way to get to the save method is if we reference the ISaveable interface. One other thing that I want to show that's important is using var since this is a common thing to do, and we'll call this implicitCatalog and we'll set this to a new catalog as well. If we try to use this variable, we see that save is not available to us and the reason for this becomes clear when we think about how var works. When we use var, the implicit catalog variable is still strongly typed and it determines that type based on what we assigned to it.
So if we hover over var, we see the implicit catalog is of type catalog. In an earlier chapter, I mentioned how I generally use an explicit interface type instead of var for my variables and this is one reason why. If a type implements an interface explicitly, then I would not have access to those interface members. Realistically, this has not been much of a problem since explicit implementation is pretty rare, but as we will see in the next chapter, default interface implementation behaves much the same as this, so being aware of interface types has become more important.
Chapter 9 How to Use Explicitly Implemented Members
Let's review what we saw in the demonstration as it relates to explicit implementation. If we create a variable with the interface type, we can call the explicitly implemented Save method. If we create a variable with a different type, such as Catalog, we cannot call the Save method.
If we use the var keyword, the compiler automatically picks the type based on what is assigned to the variable. Since it is a Catalog, that is the type for our variable, so calling Save will not work here for the same reason that it does not work for the previous example. But
we can take our Catalog variable and cast it to the interface. The cast will work since Catalog implements the interface, and after we cast the variable, we can call the
Save method successfully. It is also possible to mix methods, although this can be a bit confusing. Here we have the same interface, ISaveable, but our Catalog class has two different Save methods.
One of them is a standard method, and the other is an explicit implementation of the ISaveable interface. If we do this, then we get different behavior depending on the type of our variable. If our variable is specified as a catalog, then we get the standard Save method. If our variable is specified as ISaveable, then we get the interface Save method. And if we take our catalog variable and cast it to an ISaveable we get the interface Saved method. You may be asking why you would do this. The answer is that I would not do this, it is too easy to use the class incorrectly. But this example does show pretty clearly that the explicitly implemented member belongs to the interface and not the class. So why would we want to use explicit implementation? The short answer is that you probably do not need to use it. The code that we have seen so far is primarily so that we can understand what explicit implementation is. Generally, we do not want to hide an interface implementation from the class. The reasons for doing so involve pretty specific problems. But there is one case where we are forced to use explicit implementation, and
that is when we need to implement multiple interfaces that have conflicting method signatures. Here is an example of two interfaces that have different return types and the same method name.
We can overload methods in C# by specifying different parameters, but we are not allowed to overload them by specifying different return types. This means that if we want to implement both of these interfaces, we need to explicitly implement one of the methods so that the compiler will know which method we want to use. Here is a Catalog class that explicitly implements one of the interfaces.
In this case, the Save method is available to both catalog and the ISaveable interface. The IDbSaver.Save method is only available to IDbSaver. We could also implement it the other way where ISaveable is explicit and a standard Save method is available to Catalog and
IDbSaver. Or we could implement both interfaces explicitly. In this case, each interface would get its specified method and neither one would be directly available to the Catalog class. It can get a bit confusing, but if we have conflicting methods we'd need to implement at least one of the interfaces explicitly. One place where explicit implementation is more common is when we have interface inheritance. To explore this, we will look at the IEnumerable interface. If we look at the interface definition for IEnumerable, we see that it inherits from the IEnumerable interface. This is a bit confusing since we normally think of implementation with interfaces, not inheritance. But what this means is that IEnumerable includes all of the members from IEnumerable in addition to any members that it specifies itself. But why are we mentioning IEnumerable and interface inheritance when we are supposed to be talking about explicit implementation?
It is because of the signatures of the methods included in IEnumerable. The IEnumerable interface has a single method, GetEnumerator. This returns an IEnumerator object. The IEnumerable interface also has a single method, GetEnumerator, but this returns an IEnumerator object.
The different return types means these are conflicting methods, just like we saw earlier. And because of interface inheritance, anyone who wants to implement IEnumerable must implement both of these methods, and that means we need to use explicit implementation. We are going to do a quick demonstration to see some code in action. We will use explicit interface implementation to create a class that implements IEnumerable and also IEnumerable. Our class will generate odd numbers.
It is not very complex, but it will let us focus on the interface implementation. We are in the same explicit implementation solution that we saw earlier, but this time we're looking at the OddNumbers project. This is a console application that will output odd numbers to the screen. For this, we will create a class that implements IEnumerable. Then in our program, we can use a foreach loop to output the numbers. If we open our Program.cs file, we see that we have mostly placeholders at this point.
We do have a reference to the OddGenerator class, and that's the code that we need to fill in. So let's go to the OddGenerator.cs file and implement some interfaces. For this, we want to implement the IEnumerable interface, and as usual, I'll use Ctrl+. to let Visual Studio help us out. I'm going to choose the standard implement interface, but watch what Visual Studio does. Visual Studio recognized that we had conflicting methods. For the IEnumerable interface, it provided us with a standard implementation, but for the
IEnumerable interface, it implemented the method explicitly. Notice the method name says IEnumerable.GetEnumerator, and there is no access modifier. So now let's implement the GetEnumerator method, and for this I want to provide odd numbers. So I'll start with a variable to give us a starting point of 1, and then I will use yield return i.
If you're not familiar with yield return, that's okay, let's finish out this method and then it will make a bit more sense. So I'm going to add a while (true) loop and then increment our variable by 2, and this will give us the next odd number, and then we'll do another yield return. Doing a yield return gives us an IEnumerator, and this is the thing that makes a foreach loop work. So you can think of it
this way, if I foreach over this OddGenerator, the first time through it will give us the number 1, that's our first yield return. The next time through the loop, it will come back and start after the yield. So it will go into the while loop and increment the value by 2, and then return us the new value, which is 3. The next time through the loop, it will pick up where it left off in the while loop, and so it will iterate again and give us a 5. For our other method, we need much the same code, but the difference is we do not care whether it's strongly typed or not. Our first method must return an IEnumerator. Our second one just returns an IEnumerator, so we don't care about any specific type. And what this means is that I can return the value coming from the other method. So we can say return_this.GetEnumerator. The "this" keyword is strictly not required here, which is why it's grayed out in Visual Studio, but I like to use it in this case because the code can be a bit confusing. When I say this.GetEnumerator, I know that I'm calling the GetEnumerator method that belongs to the class, the one that is not explicitly implemented. So now that we have our implemented IEnumerable, let's use it in our code.
So we'll say foreach(int odd in generator), and I'll create a cutoff point. So if odd > 50, we'll break out of this foreach loop; otherwise, we'll write it to the console. So this should give us odd numbers that are less than 50. Let's run the application and see what we've got. And here we have the values from 1 to 49.
This is a small example, but it shows how explicit implementation works when we have conflicting members, and Visual Studio is helpful enough that it knows one of the methods needs to be implemented explicitly. This pattern where we call the nonexplicit method from the explicit method is pretty common when it comes to IEnumerable, as well as other interfaces that have both generic and versions. In summary, we have seen a bit more about the what and why of interfaces by looking at explicit implementation. When we
explicitly implement an interface member, we limit how the member is used. It is hidden from the class itself and can only be accessed by referencing the interface. In addition, we saw how explicit implementation can help us resolve conflicting methods, such as when methods differ only in their return types, and we saw this specifically with IEnumerable of T. Since it inherits from IEnumerable, it must supply both the generic and version of the GetEnumerator method. This means that the IEnumerable version needs to be explicitly implemented. This has also prepared us for the next topic, default interface implementation. As with explicit implementation, a default implementation belongs to the interface rather than the implementing class, and that means we call the interface members in much the same way.
Chapter 10 How to Change Interfaces & Default Implementation
Interfaces have remained largely unchanged since C# 1.0. So far, all of the code that we have seen reflects those original features. Interfaces have gained some significant options in C# 8. In this chapter, we will cover one of the main features, default interface implementation. We will also take a quick look at some of the other changes and see how they impact the way that we use interfaces. We will look at a bit more of the how of interfaces. One problem we may run across is how to update an interface. We will look at the problem of adding or removing members of an existing interface. One way to add members is with default interface implementation. We will see how to add a new interface member that includes both the declaration and the implementation, and we will see some of the things we need to deal with when we use default implementation. Another way to add members is with interface inheritance. This is technically not changing an existing interface, but it is another way of handling the use case. We will also look at some of the other features that changed for interfaces in C# 8. These include things such as access modifiers, static members, and a few others. With all of these interface features in mind, we will loop back to the beginning and compare interfaces to abstract classes. This will help us choose the right option based on the needs of our application.
Let's talk a bit about what happens if we want to change an existing interface by adding or removing a member. The first thing to keep in mind is that an interface is a contract. The terms of the contract are clear to both parties, both the implementers and the clients. If we start to change the terms of the contract, then the existing agreements break down. Adding a member to an interface will break implementers. Let's say that we have an ISaveable interface with a single Save method. We also have a Catalog class that implements that interface.
If we add a second Save method to the interface, then our Catalog class breaks. It does not have the second method and so no longer satisfies the interface. Our code is broken, and we get a compiler error. Removing members from an interface breaks the clients. Let's say that we have an ISaveable interface with two Save methods. We also have a client that calls one of those methods through the interface. If we remove that method from the interface, then the client is Broken. It is trying to call a method that no longer exists on the interface. We really need to keep in mind that an interface is a contract. Once it is in use by implementers and clients, we really should not change the terms of the contract. With that said, there are a couple of ways that we can add members to an interface. One way that we can add members to an interface is with default implementation. This lets us add a member to an interface along with implementation code. Since the implementation is part of the
interface, existing classes can remain unchanged. If a class does not provide its own implementation, the default implementation from the interface is used. Another way to handle the situation is with interface inheritance. Rather than adding members to an existing interface, we create a new interface that inherits from the original. The new interface includes the new members, along with all of the members of the original interface. There are pros and cons to both of these approaches. So let's look at them in more detail, starting with default implementation.
Default implementation allows us to add implementation code to the members of an interface. Here are the basics of how that works. First, an interface may include an implementation or default for some or all of its members. A class that implements the interface may provide its own implementation, and this will be used instead of the default. If a class does not provide its own implementation, the default implementation will be used. There is a lot of existing C# code out there. Since interfaces went unchanged for nearly 20 years, it is important to know where we can use default implementation and where we cannot use it. Default implementation was added with C# 8. So it will work with anything written in C# 8 or later. For the
current .NET releases, this includes .NET 5.0 and later, .NET Core 3.0 and 3.1, and .NET Standard 2.1. For earlier .NET releases, default implementation is not available. This includes all versions of .NET Framework, as well as .NET Core 2.2 and earlier, and .NET Standard 2.0 and earlier. Here is an example to show us what default implementation looks like.
Here we have an existing interface called ILogger, that has a single method, Log. In addition, we have a class called ConsoleLogger that implements the interface, and so it has the Log method. With default implementation, we can add a new member to the ILogger interface. Here we have added a LogException method. Notice that in addition to adding the declaration for the method, we have also included an implementation, a method body. In this case, it calls the existing Log method with specific arguments. With default implementation, the ConsoleLogger class can remain unchanged. Since the ConsoleLogger does not provide its own implementation of the LogException method, the default implementation is used. This means that we do
not get a compiler error after the new method is added to the interface.
Chapter 11 How to Add Default Implementation
Let's see default implementation in action. For this, we will change an existing interface and use default implementation. This will help us better understand how the compiler behaves when we add a member to an interface. Then we will provide an implementation in the class itself. This will replace the default implementation when we run our application, and this will help us see where default implementation is used and where it is not used. This time, we have the default implementation solution open in Visual Studio. This solution includes an interface for logging and two classes to implement that interface. So let's look at the code. We'll start in the People.Library project and look at the IPeopleLogger.cs file. The IPeopleLogger interface currently has a single member, a Log method. If we go to the ConsoleLogger.cs file, we'll see one of the classes that implements this interface. The ConsoleLogger implements the IPeopleLogger interface, and so it has the same Log method, and in this case, it outputs a message to the console. In the FileLogger.cs file, we have another implementation.
The FileLogger class implements the IPeopleLogger interface, and it provides a Log method that writes to a file. Both of these loggers are used in our PeopleController class. So let's go to the PeopleViewer project, open up the Controllers folder and open the PeopleController.cs file. If we scroll down to the GetPeople method, we can see that this takes an IPeopleLogger as a parameter, and it calls the Log method to let us know when the action is finished. If we scroll back up and look at our UseConsoleLogger method, we see that this news up the ConsoleLogger class and passes it to the GetPeople method.
In the UseFileLogger method, we do much the same thing just with the FileLogger class. Before running the application, I'm going to open the output folder of the PeopleViewer project. That's because this is where the file log shows up. So I'll on the PeopleViewer project and choose Open Folder in File Explorer.
And then from here, we'll click on bin, Debug, net5.0, and this is our output folder. And the file we're looking for is Log.txt, and that file does not currently exist. So let's run our application to see how the logs work. So I'll click on the Console Logger link, and then go to the console window where our application is running. Just like with our previous applications, we have two console windows open, one for the service and one for the application. We're looking for the one that says localhost:5000. That's our application. And if we look
in the console, we see our information that the GetPeople action is finished. Now let's go back to our application and click on the File Logger link. And now we'll flip back to the File Explorer window that has the output for our project. And we have a new Log.txt file, and when we open it, we see our message. So now let's close the log file and our application, and we'll go back to the code. In my UseConsoleLogger method, I have a block to catch an exception in case something goes wrong, and I want to be able to log that exception. And this is the method that we'll add to our IPeopleLogger interface. So let's go back to our interface and add a new method. I'll start by just putting in the declaration, so we'll say public void LogException, and this will take an exception as a parameter. Now if I build at this point, I will get compiler errors, and that's because our loggers no longer satisfy the interface. If we look at the ConsoleLogger, it tells us that LogException is missing, and the same is true if we look at the FileLogger. But instead of fixing the classes, I'm going to add an implementation to my interface, and this is just adding a body to this method.
So in this case, we'll call the existing Log method. We'll pass in the log level of Error, and then we'll also send the exception message. And now that we have this implementation in place, if I rebuild the application, I get no build errors. Our FileLogger and ConsoleLogger classes still satisfy the interface because that default implementation is available for LogException. So let's use this in our code. I'm going to go back to the PeopleController class and fill in this TODO section. And here we'll call logger.LogException, and then we'll pass in the exception, and I'll do the same thing for the UseFileLogger method below. So now I'll rebuild the application to make sure everything compiles.
And instead of pressing F5 to start the application, I'm going to do things a little bit differently. In this case, I'm going to on the PeopleViewer project, go to Debug, and then Start New Instance. When we do this, it will start our web application, but it will not start the service. And that means when I click on either of these links, we'll get an error because the service is not available. So let's click on the Console Logger link, and this will take a little bit longer than usual. And here we get our error screen. If I go back to the console window, we'll see that our exception is logged, and our exception says No connection could be made because the target machine actively refused it. And that's an error that we get when our service is not running. So let's do the same thing with the file logger. So we'll click on the Use File Logger, and I'll go back to the output folder. This time when we open up the Log.txt file, we'll see the same error message in our file log. So let's close our log file and our application and go back to Visual Studio. So we've seen that if our interface provides a default implementation, that will be used if the class does not provide its own implementation. But let's see what happens when a class does provide an implementation. For this, we'll go back to the ConsoleLogger.
One thing that's tempting is to go to the IPeopleLogger and use Ctrl+dot to try to implement the interface. And unfortunately, Visual Studio doesn't give us any help here. The Visual Studio tool provides us with help for interface members that are not yet implemented. Since the LogException method is implemented in the interface, Visual Studio says it's not required for the console logger, so I will not give you that option. So we'll need to add this ourselves. To make things a little easier, I'm going to copy and paste from the interface. And for the method body, I'll write a unique message to the console.
And in this code, I'm outputting three lines to the console, so we get a more detailed message about the exception. So let's build our application, and then I'll start it the same way as previously. I'll on PeopleViewer, go to Debug, and then Start New Instance.
And again, in this case, the service will not start, so we will get an error. Then we'll click on the Use Console link. And once our error screen comes up, we'll go back to the console, and we can see our more detailed error message. And if we were to look at the file logger output, it would be the same as we saw before since it still uses the default implementation. So let's go back to our interface. So what we've seen is that we can provide a new method with default implementation. When we have a default implementation, we do not need to change the existing console logger or file logger. Our code continues to build, and the default will be used for LogException. But we can also provide our own implementation for the LogException method. When we do this, the default is not used. If we do want to provide our own implementation, we need to add the methods ourselves. The Visual Studio tools, unfortunately, do not help us.
Chapter 12 How to Call Default Implemented Members
Now that we have seen how to add an interface member that has default implementation, let's take a closer look at how to call that member. Calling an interface member with default implementation is similar to calling an interface member that has been explicitly implemented.
This means that we will want to use variables with the interface type rather than a concrete glass type. When we have a default implementation, like we have with the LogException method, that implementation belongs to the interface, rather than to any class that implements that interface. If that sounds familiar, it's because we saw something similar with explicit implementation. When an interface member is explicitly implemented, it belongs to the interface type, rather than the class type. This similarity means that calling a member with default implementation works the same way as calling an explicitly implemented member.
If we use the interface type, ILogger, then we can call the LogException methods successfully. The default implementation will be used. But if we use the concrete type ConsoleLogger, we get a compiler error. The ConsoleLogger class does not have a LogException method in this case; the interface does. But because we're not referencing the interface, we cannot access that method. The same is true if we use var. As a reminder, var is a shorthand. In this case, the varLogger variable has the consoleLogger type. Because of this, we cannot access the LogException method on the interface. But just like with explicit implementation, we can use a cast. If we cast the ConsoleLogger variable to the interface type, then we can access the LogException method. In order to access an interface member with default implementation, we must use the
interface type. The interesting thing is that we may not know whether a class relies on the default implementation or not. So to be safe, we should always reference the interface when we need interface functionality. Let's explore some more code. For this, we will call an interface member that has default implementation. This will help us see how the compiler behaves when we try to access it in different ways. Here we are back in Visual Studio with the same default implementation solution open. To explore the different ways to call a method with default implementation, we will look at the PeopleController. So let's go to the PeopleViewer project, the Controllers folder, and then PeopleController.cs, and specifically, we're looking at different ways to call the LogException method. Now, if I click on LogException and press F12, it will take me to the interface, and here in our PeopleLogger interface, we can see that log exception does have a default implementation.
Now let's go back to the controller and take a look at FileLogger, and again, I'll click on FileLogger and press F12, and in this case, the FileLogger class does not have a LogException method so it relies on the default implementation from the interface.
So let's go back to the PeopleController class and change some variable types. Right now, the logger variable is specified as an IPeopleLogger, the interface, and so our call to LogException works. If I change this variable type to FileLogger, now the call to LogException does not work and the error tells us that the FileLogger class does not contain a definition for LogException, and we'll get the same result if we use var.
As a reminder when we use var, we end up with whatever is assigned to the variable, in this case, the FileLogger type. And because of this, we get the same error on the LogException method.
As with explicit implementation, we can use casting. So we can cast our logger variable to an IPeopleLogger. and now we can access the LogException method. So just like when we have an interface that has explicit implementation, we need to use the interface type in order to call an interface member with default implementation, but unfortunately, things can get confusing. Let's look at the useConsoleLogger method, this uses the ConsoleLogger class.
Watch what happens if we change this variable type to the ConsoleLogger. In this case, we do not get an error when we call LogException, and if I press F12 to look at the ConsoleLogger class, we can see why. ConsoleLogger does have its own implementation of LogException so that means we can reach it either through the class or through the interface. In either case, it does not rely on default
implementation. This is a bit confusing because sometimes the concrete type works and sometimes it does not work. It depends on whether the class relies on the default implementation or whether it provides its own implementation. The only way to be sure that it will work every time is to reference the interface type. We have seen how to add a new method to an interface and provide a default implementation. This means that existing classes that implement the interface do not need to change, but there are some things we need to consider. A big one is that we need to be aware of making assumptions about how classes will implement an interface. Unfortunately, I have run across a blog article where someone makes bad assumptions about how an interface will be implemented and that can lead to unintended behavior. Here is an example of a default implementation that makes a bad assumption.
Let's say that we have an existing IPeopleLogger interface and it only has one implementer, a console logger. If we add a new method, it is tempting to write directly to the console since that's what the console logger does, but this assumes that all loggers will use the console. If we have loggers that log to a file or do OS level trace logging, this implementation will not work as intended. This may seem obvious for this simple example, but when we are programming, it is easy to get focused on the implementation that we currently have without thinking about other implementations,
including implementations we might use for unit testing. Here is one way that we can avoid bad assumptions, use existing interface members.
In this example, we have a log exception method with default implementation, but inside the implementation, we call the existing log method. This means that if the logger logs to the console, the new method will also log to the console. If it logs to a file, the new method will log to a file so this protects us a bit. If we limit ourselves to the existing members of an interface, then it also limits how we can expand an interface. In this example, we have a repository interface that has GetPeople and GetPerson methods.
If we wanted to expand this by adding a save method, there is really no way that we can safely add a default implementation that only uses existing members, the functionality is not there. So there are some limitations to how we use default implementation and we need to be aware of assumptions that we make that could impact functionality or performance. If we keep these things in mind, then we can use default implementation safely.
Chapter 13 How to Make Bad Assumptions
This time we will purposely make a bad assumption to see how the program reacts. This will help us better understand the importance of the code that we use in our default implementations. We are back in the default implementation solution in Visual Studio. To show the result of a bad assumption, we will change the IPeopleLogger interface. So let's go to the People.Library project and open the IPeopleLogger.cs file. Our LogException method has a default implementation that calls the existing Log method, but let's change that so that it writes to the console instead. So we'll do a Console.WriteLine and then pass in a message. And to show how this works, we'll be looking at both the console logger and the file logger. I want to reset the file logger, so I'm going to open up the output location for the project.
So I'll on PeopleViewer, choose Open Folder in File Explorer, and then go to bin, Debug, net5.0. And here we have our Log.txt file. I'm going to open this and delete its contents, so now that file is empty. And I'll close this file and go back to Visual Studio. As we did before, I'm going to start just the PeopleViewer application, so we'll on the PeopleViewer project, choose Debug, and Start New Instance. And again, this starts our application, but not the web service that it requires.
So let's start by clicking on the Console Logger and wait for the error message. And if we go to our console window, we see our log entry, and this is a detailed entry that we have from a previous example. This comes from a LogException method on the ConsoleLogger class itself.
It is not using the default here. So let's go back to our application and click on the File Logger link. And now that we have the error screen, let's go back to File Explorer and open up the Log.txt file. And what we'll see is this log file is still empty, so our log did not write to the correct location. Now let's go back to our console
window. In our console window, we have an additional message, and this is coming from the LogException method on the interface itself. And this is going to the wrong location because of the assumptions that we made in our interface. The LogException method is writing to the console, and this is not a good assumption because loggers write to various locations. This example shows that we need to be careful when we add default implementation to an interface. An interface can be implemented in a variety of different ways. We need to make sure that the default implementation respects that. We cannot always avoid issues with default implementation, but if we stick to using existing interface members, we have a better chance of our code behaving as expected. Another solution to the problem of adding members to an interface is to use interface inheritance. When an interface inherits another interface, the interface includes all the members that it defines, as well as all of the members of the parent interface. So this is a way of expanding interface functionality without changing the original interface. This is not a feature that was added with C# 8. It has been around since the beginning, so we can use this in any of our C# applications. Earlier, we saw interface inheritance when we looked at the IEnumerable interface. The IEnumerable interface inherits the IEnumerable interface.
So IEnumerable includes all of the members that it defines, as well as all of the members defined in IEnumerable. We can do something similar. For our sample code, we have been using a repository. Our IPersonReader interface has a GetPeople method and a GetPerson method, but it does not have any way to change the data.
What if we wanted to add update functionality? One way to do this is with interface inheritance. Here is an IPersonRepository interface that has both read and write functionality. The write functionality comes from the AddPerson, UpdatePerson, and DeletePerson methods in the interface. It inherits the IPersonReader interface.
This means that it also includes the read operations, GetPeople and GetPerson. When a class implements the IPersonRepository interface, it must implement all of the methods. Here we have a SQLRepository class that implements the IPersonRepository interface.
In the class, we have implementations for all of the members for both interfaces. One thing that I like about using interface inheritance is that it keeps our interfaces focused.
If a part of the code only needs read operations, it can use the IPersonReader interface. If a part of the code needs both read and write operations, it can use the IPersonRepository interface. The same is true of classes that implement the interface. If a class cannot update data, for example, it accesses a web service that only has read operations, it can implement the interface. If a class can support read and write operations, then it can be expanded to implement the newer read/write interface.
The downside is that there are more changes required to implement the new functionality. Unlike default implementation, no class gets the new functionality automatically. Each tool we use has both good and bad aspects. Our challenge is to maximize the good.
Chapter 14 Additional Features & Abstract Classes
So far, we have used default implementation with methods, but we can also use it with other interface members such as properties. Default implementation can be used with properties, but it really only makes sense for or calculated properties, meaning properties that have getters, but no setters. Interfaces are not allowed to have fields, so it is difficult to have a setter on a property with default implementation. What value do we set?
Because of this, it is best to think of default implementation only on the get part of a property. Another limitation has to do with automatic properties. Back in our first example at the beginning of this book, we saw a concrete regular polygon that has two properties with empty get and set blocks. These are automatic properties, meaning the compiler creates the backing field and default behavior for us. But when we looked at the IRegularPolygon interface, we had the same syntax, but the meaning was different. In an interface where we have empty get and set blocks, this is a declaration, meaning that an implementing class must have these properties with both getters and setters, but the implementing class must provide them. Unfortunately, there is no way to use default implementation to create automatic properties in an interface. This is something that I would like from a functional standpoint, but interfaces are abstractions, and properties require data that belongs to a particular instance of a class. So even though it would be useful, it does not fit in with the abstraction that interfaces provide. Another feature of interfaces is that we can have access modifiers on the members. Prior to C# 8, access modifiers were not allowed. Everything was automatically public. That is why it's common to see interfaces with no access modifiers on the members. They were not allowed. Now interfaces can have members that are explicitly marked as public. This is the default value. So older interfaces without access modifiers continue to work in the same way. Public members are visible to anyone with access to the interface. Interface members can also be marked as private. These members are only visible inside the interface itself. Because of this, they must have a default implementation. If the implementation is not provided inside the interface, there is no other way to provide it. The good news is that the compiler will force you to provide an implementation for private members. Interface members can also be marked as protected or
internal or the various combinations. You probably only want to use these for very specific cases with a combination of interface inheritance and default implementation. Things can get confusing pretty quickly in these situations. If you want to take a deeper dive, you may want to study some of these features in more detail. I gave some recommendations on how to use default implementation with properties. You may want to look into the reasons behind this. The same is true for access modifiers. I showed some of the basic rules, but you may want to look up some code samples for this. Another thing that was added in C# 8 is the ability to have static members on interfaces. Static members on interfaces work the same way as they do with classes, so we will not spend time looking at the specifics. And there is also an article on using static members with interfaces. In addition to the articles, the additional resources will contain updated code samples as new versions of .NET and C# are released, so be sure to check them out. Back at the beginning of the book, we used regular polygons to explore the similarities and differences between interfaces and abstract classes. They share common features, such as having abstract members and allowing for implementation code. Now that we have learned a bit more about interfaces, let's compare specific aspects. On a conceptual basis, the primary purpose of an interface is that it defines a contract. The purpose of an abstract class is to have some shared implementation along with some abstractions defined. A class can implement any number of interfaces. Earlier, we saw that List of T implements seven different interfaces. In contrast, a class can inherit from only a single base class. When we use an abstract class, it fills that inheritance slot. An interface has limited implementation code. We can use default implementation, but we cannot create automatic properties or reference fields that relate to a specific instance of a class. An abstract class has no limitations on the implementation code.
Abstract class implementation can have any implementation that we would have in a concrete class. An interface cannot define automatic properties, it can only declare that the properties exist. It is up to the implementing class to provide those properties. An abstract class can have automatic properties and these are automatically available to all of its descendant classes. The final difference is about what types of members we're allowed to use. Interfaces are limited to properties, methods, events, and indexers. Abstract classes can have properties, methods, events, indexers, fields, constructors, and destructors. Some of these differences are subtle, so how do we decide whether to use an interface or an abstract class? When I need to choose, I ask myself how much implementation code is shared. If there is a lot of shared code, then I lean towards using an abstract class. If there is little or no shared code, I lean towards an interface. I like to use interfaces where I can since a class can implement multiple interfaces and it also leaves the inheritance slot open so I can inherit from another class. Let's take a second look at some of our samples. Here is the regular polygon that we saw in the first example. Let's think about how much code is shared by all of the different shapes, square, triangle, octagon, and others. The NumberOfSides property is shared by all of the shapes.
The SideLength property is also shared. The GetPerimeter method is shared as well. The calculation for perimeter is the same regardless of how many sides a polygon has. The GetArea method is not shared. The calculation for area is different for each shape. Based on how much shared code we have, I would choose an abstract class if I needed to create a regular polygon.
Our data readers are a bit different. Here, we have the GetPeople implementation from three data readers, the service reader, the CSV reader, and the SQL reader. These implementations have no shared code, they all do things much differently. Because of this, I would use an interface to create these data readers. This also leaves the inheritance slot open. As an example, I could have multiple data readers that access relational databases, such as Microsoft SQL Server, Oracle, and MySQL. These are likely to have common elements such as connection strings, and I could put those into a shared base class. By using an interface for the data reader functionality, I leave a base class open as an option. In this chapter, we looked at more of the how of interfaces. One question is how to update an interface. An interface is a contract. If we add or remove members from an interface, it changes the contract and may break code. We did see two options if we need to add a member to an interface. One way to add a member is with default interface implementation. This is an interface member that includes both the declaration and the implementation code. This allows us to add a member without breaking existing classes that implement the interface. We also saw that we must use the interface type to call the default implementation the same as we do with explicit implementation. Another way to add members is with interface inheritance. We do not change the existing interface. Instead, we create a new interface with the new members and inherit from the original interface. We need to change more code for this option, but it also gives us more choices for how we break up our code. We also saw some of the other features that were changed with interfaces in C# 8, including access modifiers, static members, and a
few others. As the last stop, we looked at the differences between interfaces and abstract classes. As a general rule, when I have shared code, I use an abstract class. When I have little or no shared code, I use an interface. As with most of our tools, we need to look at the needs of a specific application in order to make the best choice. I hope that you have learned a lot and that you are better able to use interfaces effectively in your code.
BOOK 6
C#
GENERICS
PERFORMANCE AND TYPE SAFETY
RICHIE MILLER
Introduction
In this book, you will learn the basics about generics and their advantages, such as type safety and code reuse. First, I will show you how this book is structured, and you will learn about the scenario that we will use in this book. After that, you will implement a stack class that can store double values. Then you will get a new requirement to make the stack class work not only with double values, but with any type. You will learn different approaches on how to achieve this. First, you will use the object type instead of double to make the stack class work with any type. But this approach has some disadvantages, like, for example, no type safety. The next approach is to copy and paste the stack class for every new type that needs to be supported. That approach has type safety, but it leads to code duplication. Finally, you will create a generic stack class that will be type safe and that will avoid code duplication. Beside creating your own generic type, you will also learn in this chapter how to use existing generic classes that are available in .NET. Now let's start and let me explain how this book is structured. In this book, we will use a scenario where a company runs several coffee shops around the world. Now they want a .NET console application to load and save employees and organizations that visit their coffee shops. This application will be a prototype for their developers and it should show how to write C# code that will work with any data storage. The great thing is, they want you and me as a team to
build this .NET console application, but before we start building that application, I will ensure in this chapter that you understand the need for generics in C#. So in this chapter, I will prepare you for the project at a Company. In the next three chapters, you will build the .NET console application together with me for a Company. While building that .NET application, you will learn how to implement and use generic classes, you will learn how to build and use generic interfaces, and you will learn how to create and use generic methods and generic delegates. Then, after we have built the .NET console application for a Company, I will ensure in the last chapter of this book that you know the special cases with generics; for example, how static members behave if you define them in a generic class. Now let's begin implementing a stack class to store double values.
Chapter 1 How to Implement Stack Class for Doubles
In this chapter, you will create a class called SimpleStack that has a push method to push a double value into it. As you can see, the double values are stacked on top of each other. You will also create a count property that returns how many values are stored in the stack, and you will create a pop method that reads and removes the value from the top of the stack. Before we start implementing this SimpleStack class, I want to mention that .NET contains already a Stack class. So, you might ask yourself, why are you implementing another Stack class? That's a great question, and there are two reasons why we do this in this chapter. The first reason is that implementing a Stack class is a great exercise for you as a C# developer. The second, and more important reason, is that it will help you to understand the need for generics in C#. Now in this demonstration, you will learn how to implement a SimpleStack class for doubles. Here I've opened Visual Studio 2019. Let's go to File, New, Project. In the new project dialog, I have selected here already C#. Now let's select here the Console Application project template and let's click the Next button. I keep here .NET 5 as a target framework, and I click the Create button to create the console application.
Now let's remove here the Console.WriteLine statement and let's create here a stack variable and let's initialize it with a SimpleStack instance. I press Ctrl+dot, and now you can see Visual Studio suggests here to generate a class, SimpleStack, in a new file. Let's hit Enter, and now you can see here in the Solution Explorer the new SimpleStack C# file.
Now on that stack, let's call a Push method and let's pass in a double value. I press Ctrl+C and Ctrl+V, and again Ctrl+V, to copy this line two times. Let's also push in the double value 2.8, and let's also push in the double value 3.0. Now I press Ctrl+dot again to
generate this Push method in the SimpleStack class. Let's hit Enter, and now let's go to the SimpleStack C# file that contains the SimpleStack class. First, let's make the class public, and let's also make the push method a public method. Now let's also give here the parameter the name item.
Now we need to store that item in our SimpleStack class. To do this, I initialize here in the constructor an items field with a new double array that has a length of 10 items. Let's press Ctrl+dot to generate a _items field in our SimpleStack class. This means now that our SimpleStack class will work with a maximum 10 items, which is okay for our scenario. Now let's implement the Push method. Here, let's use our items array.
Let's just wrap here index 0 and let's assign the item to index 0. This works actually only for the first item. For the second item, we should use index 1, and for the third item, we should use index 2, and so on. This means we need to know the current index of our stack. So let's create another private field in the class of type int, and let's call it _currentIndex, and I initialize it with This means if no item is in our SimpleStack, the current index is Now we can use, instead of 0, this currentIndex field. But before using it, we need to increment it by 1. So before this assignment, let's use the currentIndex field and let's add 1 to that field. Instead of writing it like this, you can also write it like this with += 1. And instead of writing it like this, you can use the increment operator to increment the currentIndex field by 1. This means we don't need this extra statement here and you can directly increment the currentIndex field
here. But writing the increment operator after this field means that the field is incremented after this statement. This means when you call the push method for the first item, that item is assigned to the index instead of the index 0. Using the index will throw an exception. So, instead of incrementing the currentIndex field after this statement, you should increment it before this statement. To do this, you write the increment operator before the field.
Now when the push method is called for the first item, the currentIndex field is incremented from to 0 and the first item is stored at index 0. Now let's get rid here of the using for the System namespace as we don't need that using directive. Next, as we have only single statements in the constructor and in the push method, let's use expression bodies. So here in the constructor, let's use an expression, and now we can remove the curlies and now we have an expression body for the constructor. Let's do the same with the Push
method. Let's also remove the curlies, and now we have a nice and clean Push method.
Next, let's implement here the Count property for our stack. The Count property just returns currentIndex +1. This means if currentIndex is count will be 0. There is no item in our stack. Now the final bit that we need is the pop method. That one should return the item from the top of the stack and it should also remove the item. So let's use also here an expression body. Let's grab from the items array the item at the currentIndex.
After this statement, you need to decrement the currentIndex field by 1, so let's write here after the currentIndex field two minus signs, which is the decrement operator. This SimpleStack class is implemented, and we can use it in the Program.cs file. So let's go to the Program C# file. Here we use already that SimpleStack class, and we push three double values into it. Now let's read the double values from the stack and let's write them to the console. So let's create here a while loop and let's say while(stack.Count > 0), and then we just grab the item by calling the pop method. Instead of using var here, I write here explicitly double so that you can see the type. Now let's use here the cw snippet. You can press the Tab key twice to insert a Console.WriteLine statement. So I press the Tab key twice, and now you see here Console.WriteLine. Let's write to the console Item:, and then the double value. Now in addition, I also want to calculate the sum of the double values. So let's create here a variable, double sum, and I initialize it with 0.0. And in this while loop, let's write sum += item to add the popped double value to the sum variable. After the while loop, let's create another Console.WriteLine and let's write out the sum to the console. At the end of the program, I also add the Console.ReadLine statement so that the console stays open until the user hits the Enter key. Now let's run the application and let's see this in action. As you can see, the three double values are printed to the console in the reverse order like they were added to the stack. Three is at the top, then 2.8, and then 1.2, and the sum is 7. So, you have implemented now the SimpleStack class that works with double values. In the next chapter, you will learn about the new requirement to make the SimpleStack class also work with string values.
Chapter 2 Building SimpleStack instance
Previously, you have implemented the SimpleStack class that can store double values. Now the new requirement is that you can use a SimpleStack instance also to store string values. Before we look at different approaches to achieve this, let's switch now to Visual Studio and let's prepare the code of our application for the new requirement. Here I've opened the Program.cs file.
Now let's select all the code in the Main method except the Console.ReadLine statement. I press Ctrol+dot and Visual Studio suggests me to extract the method. Let's hit Enter to do this, and let's call the method StackDoubles, and I hit Enter to accept that name. Now below the call of this StackDoubles method, let's call a new method that we name StackStrings. I press again Ctrl+dot to generate the StackStrings method in our Program class. Let's hit Enter. Now you see here the new static StackStrings method, and I
just cut that method from here and I paste it below the StackDoubles method so that the methods are defined in the order like they are called in the main method. It's not necessary to order the methods exactly in that way, but I think that the code is more readable if you do this. So now when you look into the StackDoubles method, you can see there everything that we have done Previously. Now in the StackStrings method, let's also create a new SimpleStack instance. On that stack, let's call the Push method and let's push in two strings.
Now when you hover these lines, you can see cannot convert from string to double because our SimpleStack class works only with doubles. So next, let's look at different approaches how we can make the SimpleStack class work also with strings and any other type. In this demonstration, you will learn about different approaches to make the SimpleStack class usable with any type. The first approach is to use object instead of double in the SimpleStack class to make this possible. But this approach has some disadvantages, like, for example, no type safety. The next approach that you will learn is to copy and paste the SimpleStack class for every new type that needs to be supported. This approach is type safe, but it has the disadvantage that it leads to code duplication. Finally, you will learn how to create a generic SimpleStack class that works with any type that is type safe and that lets you reuse your code. Now let's start with the object approach and Here I've opened the Program.cs file of our application, and you can see the StackStrings method where we try to use the SimpleStack class with strings.
When I hover a string, you can see here the error cannot convert from string to double. Now let's go to the Solution Explorer and let's open here the SimpleStack class. When I select double, you can see the four occurrences of this double type in our SimpleStack class. Now let's use instead of double the object type. So I copy here object and I replace the other occurrences of double with object. Now we have a simple stack implementation that works with any object.
So let's go back to the Program.cs file. As you can see, the red underlines under these two strings disappeared. This means we can now also use that stack with strings. Let's also create here a while loop, and let's say while(stack.Count > 0), then we just do a Console.WriteLine and we pop the string from the stack. But now when I go to the Error List in Visual Studio, you can see here an error, Cannot implicitly convert type object to double. When I this, you can see this is here in the StackDoubles method.
As the Pop method now returns an object, you need to explicitly cast that object into a double. So now let's run our StackApp and let's see if everything works. Everything works. Here you can see the SimpleStack with the double values, and down here the SimpleStack with the two string values. But now, this object approach has two big disadvantages. The first is boxing and unboxing. When you hover the push method, you can see it takes now an object. Object is a reference type, and these double values here are value types.
This means these double values are boxed now into an object, and down here where you cast the object back to a double, they are unboxed again. Boxing and unboxing means that in .NET memory, the double values need to be copied to another place, and this costs performance. You might not notice this when you work just with three double values, like in our application here, but if you work with a lot of data, then you should avoid boxing and unboxing because it has really a performance impact. You can actually see the boxing and unboxing when you look at the generated intermediate language code. Let me show you how to do this. Let's go to Tools, Command Line, and let's open here the Developer Command Prompt. In the command prompt, I write here ildasm, which stands for Intermediate Language Disassembler Let's hit Enter, and this tool comes up. Now
let's go in Visual Studio to the Solution Explorer, let's our project, and let's select from the context menu Open Folder in File Explorer.
Here in this project folder, let's navigate to bin/Debug/net5.0, and here you see the .StackApp.dll file that contains our application code. Now let me bring up the Intermediate Language Disassembler window again, and I just drop here this .dll file into the Intermediate Language Disassembler. Let's expand here the StackApp namespace, let me make this window a bit bigger, and let's expand here the Program class, and here you can see the StackDoubles method. When you on this method, another window pops up with the Intermediate Language code. So let me maximize this window. Here you see the constructor call of the SimpleStack class. Then you see here the first double value, 1.2, and you see here box where it is boxed into an object container and then the push method on the SimpleStack instance is called. Next, you'll see here the 2.8 double value. As you can see, this value is here 2.7999, and so on. The double type in .NET has a precision of 15 to 16 digits.
When you count the digits here, you will notice that these are 16 digits and digit number 17 is not precise anymore. If you need a higher precision, you can use .NET's decimal type. But now, let's focus again on boxing and unboxing. After the 2.8 double value was created, it is also boxed into an object and then passed to the Push method of the SimpleStack instance. Here is the 3.0 double value. It is boxed into an object container and pushed into the SimpleStack.
When I scroll down, you can see here the call of the Pop method of the SimpleStack instance, and here the double value is unboxed again into a value type.
So, this boxing into object containers is a disadvantage of this object approach. It means that value types like these double values are copied in .NET memory into an object container, which has a performance impact, and that impact is especially important when you work with a lot of data. Now let me close these windows.
Besides boxing, another disadvantage of the object approach is that the SimpleStack class is not type safe anymore. So here, instead of pushing doubles into our stack, you could also push, for example, a bool value into the stack or I could just push in a new object as it works with any object. The original idea was that you can push only double values into this SimpleStack instance here, but now with the object approach, which you can push anything into it. So, the object approach has the advantage that you were able to reuse the existing SimpleStack class for doubles and strings and for any other type. But it has two disadvantages. Value types are boxed here, and the
SimpleStack class is also not type safe anymore. Next, let's look at the copy and paste approach to make the SimpleStack class work with different types. Previously, you learned how to make the SimpleStack class work with any type by using here object instead of double. This approach allows you to reuse your existing code. But as you know, it has two big disadvantages. Let's go to the Programs.cs file. Here, when you push double values into the SimpleStack, these double values are boxed into object containers as the Push method takes an object. So this boxing is the first disadvantage, and the second disadvantage is that our stack is not type safe anymore. Here we want to use a SimpleStack with double values, but as you can see here, you can push anything into that stack. Now let's take a look at another approach to make the SimpleStack class work with any type, and that approach is copy and paste.
Let's go to the SimpleStack C# file. Here I call the SimpleStack class SimpleStackDouble. Let me copy this, and let's also call the constructor, like that. Now, I replace all the occurrences of object with double so that this class works only with double values. Now, we need another SimpleStack implementation that works with strings, so let me copy this class and I paste it again into this file. I call the new class SimpleStackString. Let's also rename the constructor here to SimpleStackString. Now this class uses, instead of double, strings. So let's replace double with string. I do this on all occurrences.
Now let's go back to the Program.cs file. Here in the StackDoubles method, let's use the SimpleStackDouble class.
Now, as the push method takes a double, these double values are not boxed anymore into an object container. Also down here, you can
see that you cannot push anything else than a double value into that stack. So when I hover, for example, the string Thomas, you can see here the error cannot convert from string to double. So let me remove here these invalid statements. The next thing is that the Pop method down here now also returns a double. This means this cast in here is not needed anymore.
Now let's go down to the StackStrings method. Here in this method, let's use the SimpleStackString class, and when I hover the Push method of this class, you can see it takes a string.
So also here, if you try to push anything else into that SimpleStackString, like, for example, a bool value, you get an error like here, cannot convert from bool to string.
So now we have back type safety and performance because no boxing or unboxing happens. But the issue is now that for every new
type you need to copy here this SimpleStack class. This means you cannot reuse your existing code. In the next chapter, you will learn how you can reuse your code by implementing a generic SimpleStack class.
Chapter 3 How to Create Generic Stack Class
Previously, you learned about the copy and paste approach to make the SimpleStack class work with different types. We have copied here this SimpleStackDouble class and we have created the SimpleStackString class. The only difference is that we use here the type string instead of double. The rest of the code is exactly the same. So this copy and paste approach leads to code duplication. You cannot reuse the existing code. Now let me show you how you can reuse the existing code by creating a generic SimpleStack class. So let's remove here this SimpleStackString class. Now let's scroll up to the SimpleStackDouble class, and let's call this class just SimpleStack. I also rename the constructor.
Now, the issue that we have is we want to replace these four occurrences of the double type when the SimpleStack class is used. So, we don't want to hard code the double type into this class. We actually need something like a type placeholder that can be specified when the SimpleStack class is used. So, I replace all these double types with TypePlaceholder. Now, the great thing is you can do exactly this with generics. This is what generics are made for. To define the TypePlaceholder, you add a pair of angle brackets after the class name and then you use the TypePlaceholder name.
Now you see the class doesn't have any errors anymore. This is now a generic type parameter that you need to pass to the SimpleStack class when you use the SimpleStack class. But before we use it, I want to tell you a few more things how you should name this TypePlaceholder. You can give it any name you want, but the recommendation is to call this just T, or if you want to specify a more specific name, you can call it, for example, in this case TItem. But as you see, you always start with an uppercase T.
In this case, I think it's obvious what T is, so let's just call it T. So in the code, instead of TypePlaceholder, I use everywhere just T. But it is a TypePlaceholder for the type that needs to be specified when the SimpleStack class is used. Now you have created a generic SimpleStack class. You read this class name actually as SimpleStack of T. Now, if you use this class, for example, with the type double, all these occurrences will be of type double. If you use it with a type string, all these Ts that you see here will be strings. Now let's go back to the Program.cs file and let's use the generic SimpleStack class.
So here in the StackDouble method, let's create a SimpleStack instance and let's add an opening angle bracket to pass the generic type argument to the SimpleStack class. Here, we want to use the SimpleStack class with the type double.
Now as you can see, the code in this StackDoubles method does not have any errors. When you hover the Push method, you can see it takes a double.
This means no boxing happens and the SimpleStack instance is also type safe. When you try to push, for example, here a Boolean into the SimpleStack of double, then you can see you get the error here, cannot convert from bool to double. So let's remove this again. Also when you hover the Pop method, you can see it returns a double, so everything is typed.
Now let's scroll down to the StackStrings method. Here in the StackStrings method, we need a SimpleStack that works with strings. So let's create here a SimpleStack of string. Now when you hover here in this case the Push method, you can see it takes a string. Also, the Pop method returns a string, so everything is type safe.
Now let's run the application and let's see if it still works.
Yes, it comes up, and you can see here the output from the SimpleStack of double and here the output from the SimpleStack of string. In the next chapter, let's wrap up the advantages of the generic SimpleStack implementation.
Chapter 4 Advantages of Generics
To make our SimpleStack class work not only with doubles, but with any type, you have learned about different approaches Previouslys. First, you learned about the object approach. Using the object type instead of double meant that we were able to reuse the existing code. But with this approach, we lost two important features.
Our SimpleStack class was not type safe anymore and we were able to push any type into the stack. We also lost performance because value types, like double values, were boxed into an object container, and when you read them from the stack, you need to unbox them again with a casting. This boxing and unboxing means that in .NET memory, copy operations are necessary, and these copy operations cost performance, which is especially important when you work with a lot of data. So, after the object approach, we used copy and paste. For every type that we wanted to support, like double and string, we have copied and pasted the SimpleStack class. This means we got type safety and performance, but we were not able to reuse the code like with the object approach. So, you see, both approaches have their own advantages, but you don't get all three advantages with these two approaches. And this is where generics enters the stage.
With generics, you get all these advantages that you see here. You can reuse your code. We have written in this chapter just a single SimpleStack class that works with any type because it is generic. It is also type safe. When you create, for example, a SimpleStack of double and you try to push a string or any other type that is not a double into that stack, your code does not compile. It is also performant.
When you create a SimpleStack of double, the Push method takes a double, which means the double doesn't have to be boxed into an object container.
These are the advantages of generics in C#. Now let's use the generic Stack class of .NET. .NET contains many generic classes, maybe the most famous generic classes are the collection classes in the namespace System.Collections.Generic. This namespace contains several generic classes, including the List of T class, the Queue of T class, the Stack of T class, and the generic dictionary class that has the two generic type parameters, TKey and TValue. To create an instance of this generic dictionary, you use the class with two type arguments that are separated with a comma, like here, int and string.
So here, the dictionary.Add method takes an integer and a string as arguments. But now, in this chapter, let's focus on the generic stack of T glass. The Stack of T class has, like the SimpleStack of T class that we have implemented in this chapter, the Push method, a Count property, and a Pop method. This means in the console application that we have built you can use the Stack of T class instead of our SimpleStack class without changing any other code, as it works exactly the same way. Now in this demonstration, let's do this. Let's use the Stack of T class of .NET. Here I've opened the Program.cs file of our console application. Now down here in the StackDoubles method where we create a SimpleStack of double, let's remove the Simple from the class name and let's use the Stack class.
I press Ctrl+dot, and you can see Visual Studio suggests to me here to add a using directive for the System.Collections.Generic namespace.
Let's hit Enter, and now we have here the using directive in the Program C# file, and you can see the generic Stack class is found.
When you look now, for example at the Push method, you can see it takes a double. When you look at the Pop method, you can see it returns a double. And then there is also the Count property that is also of type int, like in our SimpleStack class. Now let's also scroll down to the StackStrings method and let's also use here the Stack class of .NET. Now let's run the application to see if everything still works. Yes, it works. First, you see here the double values from the Stack of double and then here the two strings from this Stack of string instance. Now let's wrap up what you have learned so far. You learned about different approaches on how to implement a SimpleStack class that works as a storage for any type. The first approach was to implement the SimpleStack class by using the object type, but this approach had no type safety and, in addition, bad performance because value types were boxed into objects. The next approach was to copy and paste the SimpleStack class for every type that needed to be supported. This approach had type safety, but it led to code duplication. Finally, you learned how to create a generic SimpleStack of T class that is type safe and that can be used with any type. You also learned about the advantages of generics, which are code reuse, type safety, and performance because no boxing happens for value types. In the last chapter, I showed you how to use the existing generic Stack of T class that is available in .NET. Now you learned why generics are needed in C#. In the next chapter, you will start together with me to build the .NET console application for a Company, and while building that application, you will learn more about implementing generic classes.
Chapter 5 How to Create .NET Console Application
In this chapter, you will learn how to build generic classes with single and multiple generic type parameters, and also with generic type constraints. First, I will ensure that you understand the scenario that we will use in this chapter. You will build a .NET console application for a Company. After you are familiar with the scenario, you will learn how to implement a generic class, and you will also learn how to inherit from a generic class, and how to use multiple type parameters on a generic class. Finally, you will learn how to add generic type constraints to a generic class. Generic type constraints allow you to ensure that a generic type parameter fulfills specific requirements. Now let's start and let me show you the scenario. In this chapter, you will start to work for a Company, which is a company that runs several coffee shops around the world. They want a .NET console application to load and save employees and organizations that visit their coffee shops. This application will be a prototype for their developers and it should show how to write C# code that will work with any data storage. So, you might ask yourself how can you write C# code that works with any data storage? That's a great question. Let me explain the plan. We will create a .NET console application, and in that application we will create an Employee class. To save an employee to data storage, like for example a database or file, you could write the data code in the main method of our console application. But this would mean that the whole application would be data So, instead of doing this, we
will create an EmployeeRepository class that encapsulates the detail storage access. It will contain an add method to add an employee and a save method to save the employees to the data storage. This means you will implement all the data code in the EmployeeRepository class and the rest of the .NET console application works with any data storage as it just depends on the repository. Now, besides the employees of a Company, also other organizations visit their coffee shops and these organizations need to be stored as well. To do this, you could create an OrgaRepository class with an Add and a Save method that saves organizations to data storage. But now you can see already that the EmployeeRepository and the OrgaRepository are very similar. Only the parameter of Add method is different. In one case it's an employee and in the other case it's an organization. So, instead of creating two individual repository classes, you will create a GenericRepository class. It will have an Add method that takes an item of type T and it will have a Save method to store the items. As a data storage, you will use in this chapter a simple List of T to store the data just in memory. But before you will create this generic repository of T class, you will create a .NET console application with a EmployeeRepository class. This EmployeeRepository class will store the employees in a List of Employee. In this demonstration you will create a .NET console application, and you will add an Employee class to it, and you will learn how to build an EmployeeRepository class that stores employees in a List. Here in Visual Studio, let's go to File, New, Project. In this dialog, I have set the filter here already to C#. Let's select here the project template for our console application, and let's click the Next button. Let's click the Next button. I keep here .NET 5.0 as a target framework, and I click the Create button. Now let's go to the Solution Explorer, let's the project, and let's select from the context menu Add, New, Folder. I call this
folder Entities. Next, I this folder, and I select here from the context menu Add, Class. Let's call the class Employee, and let's click the Add button. First, I put the cursor here on the usings, and I press Ctrl+. to remove the unnecessary usings.
Next, let's make this Employee class public. Now, let's use here the prop snippet. I press the Tab key twice to insert a property of type int, and the property should have the name Id. Now let's use the prop snippet again, and let's create a property of type string that has the name FirstName. Now I want to do an additional thing. Let's go to the Solution Explorer, and let's the project to open the csproj file. Here in the csproj file, I add a nullable element, and I set it to enable. This enables nullable reference types. It means that you will get additional compiler warnings that help you to avoid null reference exceptions. Let's save the csproj file, and let's close it.
Now back in the Employee class, you can see that the FirstName property is underlined in green. When you hover over it, you can see a warning in the tool tip that says that the property FirstName must contain a value when exiting the constructor. Consider declaring the property as nullable.
This means in the constructor, or directly here, you have to assign a value, or you have to mark the property type as nullable. Let's do this. After the string, let's add a question mark to mark this property as nullable. This means where you use this FirstName property, the C# compiler knows now that this property can be null, and it will give you additional hints to avoid null reference exceptions.
Now besides these two properties, let's override the ToString method. Instead of using here curlies with a method body, let's use an
expression body, and as a string, let's write the {Id}, and let's also write out the value of the {FirstName} property.
Now we have created here a simple Employee class. Next, let's create an employee repository. So let's go again to the Solution Explorer. Let's our project, and then select from the context menu Add, New, Folder. Let's call this folder Repositories. I hit Enter, and I this folder, and I select from the context menu Add, Class. Let's give the class the name EmployeeRepository, and let's click the Add button. Now, also in this class, I put the cursor on the usings and I press Ctrl+. to remove the unnecessary usings.
also make this class a public class. Now, in this repository class, let's create here an Add method that takes an employee as a parameter. I press Esc to close Intellisense, and I press Ctrl+. to add
a using for the Entities namespace that contains the Employee class. Let's give the parameter the name employee, and now inside this Add method, we add the employee to a list. So here, in our class, let's define here a private readonly field of type List, and let's give this field the name _employees. I press Ctrl+. to add a using for the System Collections Generic namespace so that we get the List class. Now, let's initialize this field here with the new List. Now, what you can see here is that we have the generic class on the left side of the equals sign and also on the right side.
So you might want to try to use here, for example, the var keyword, but as you can see, it is underlined in red. When you hover over it, you can see that the keyword var may only appear within a local variable declaration or in script code.
The keyword is not valid for fields, so let me press Ctrl+Set to use again the List class. But now, since C# 9.0, you can use target type new expressions that allow you to omit the class name on the right side of the equals sign, if it is already defined on the left side, like we have it here. This means you can omit here List.
So for generic classes, you also omit the generic type arguments like here, the Employee class. So let's remove this, and now you have a much shorter field initialization. Now in the Add method, let's use that employees field, and let's add the employee to that List.
Besides the Add method, let's also implement here a Save method in our EmployeeRepository class. In the Save method, let's create foreach loop, and let's say for each employee in that employees list, and we write the employee to the console. So I'll write here, cw for the Console.WriteLine snippet, I press the Tab key twice, and I just write here the employee to the console. Let me remove here the system namespace. I press Ctrl+., and I add a using for the system namespace. Now this little employee repository class is implemented, and you can use it in the Main method. So let's open the Program.cs file. Here in the Main method, let's create an employeeRepository variable, and let's initialize it with a new EmployeeRepository. I press Ctrl+. to add a using for the repository's namespace. On that employeeRepository, let's call the Add method to add a new employee. Let me remove here Entities from the class name, and let's press Ctrl+. to add a using directive for the Entities namespace.
Now on that employee, let's set the FirstName property to Julia. Let's add a semicolon, and I press Ctrl+C to copy this line, and I press two times Ctrl+V to paste it. Let's add another employee with the name Anna, and let's add another one with the name Thomas. After the three employees are added, let's call on the employeeRepository the Save method.
And finally, let's add here the Console.ReadLine statement so that the user has to hit the Enter key to close the application. Now let's run our application to see it in action. As you can see, the three employees, Julia, Anna, and Thomas, are printed to the console. In the next chapter, you will learn how to change this EmployeeRepository class to a generic repository class.
Chapter 6 How to Implement Generic Class
Previously, you created a .NET console application with the classes Employee and EmployeeRepository. Now, besides the employees of a company, also other organizations visit their coffee shops, and these organizations need to be stored as well. To do this, you could create an OrgaRepository class that has an Add and a Save method. But instead of creating another repository class, you will change the EmployeeRepository class to a GenericRepository class. Then, the Add method won't take an employee, but an item of type T. This means that you can use this GenericRepository class, not only for employees, but also for organizations and any other type. So, in this demonstration, you'll learn how to implement a GenericRepository class. First, let's add the Organization class.
To do this, I just open the Employee.cs file, and I copy here the Employee class, and I paste it again into this file. Let's call the new
class Organization. And instead of a FirstName, it has just a Name property. So in the ToString method, let's adjust this as well,. Let's use here the Name property. Now I put the cursor behind the Organization class, and I press Ctrl+dot, and you can see Visual Studio suggests me here to move this type to an Organization.cs file. Let's do this by pressing the Enter key. When you go now to the Solution Explorer, you can see we have here this new file. Now let's open the EmployeeRepository class. Let's change here the class name to GenericRepository. I press Ctrl+dot to rename also the file to GenericRepository.cs.
Now on this class, let's define here a generic type parameter with the name T. Let's go down a bit. Here, we create a List. As we have now a Generic class, this should actually be a List of type T. Let's put the cursor on the field, and I press Ctrl+RR to rename that field, and I call it _items. Now here, the Add method should not take an
employee. Instead, it should take an object of type T. Let's call this parameter here, instead of employee, just item. I press Ctrl+dot to rename it. Now finally, down here in the foreach loop, let's call here the var other name, also item, and I press Ctrl+dot to rename it. Now you see we have a GenericRepository class. Let's toss the objects in this internal List. So when you use it with the type employee, this will be a list of employee, and this parameter will be of type employee. When you use it with an organization, it will have a list of organizations, and this parameter will also be of type organization. Now let's use this class in the Program.cs file. So here, we have still this EmployeeRepository class that does not exist anymore. Let's change this now to a GenericRepository. After this block, let's also create an organizationRepository. Let's initialize this one with a new GenericRepository. On that one, when you call the Add method, you can see it takes an Organization. So let's add here a new organization, and let's set the Name. I press Ctrl+C to copy this line, and I paste it again. Let's add another organization. Finally, let's call on the organizationRepository the Save method. now you have implemented a GenericRepository class that works with any data type. Next, you will learn more about generic classes and inheritance.
In this demonstration, you will learn how to inherit from a generic class. Previously, you have created this GenericRepository class. Now let's say that you want to create a subclass of this RepositoryClass that has also a Remove method. So first, let's make here this items field a protected field so that you can access it from a subclass. Now let's scroll down and let's create in this file a subclass.
So here let's create, for example, a concrete EmployeeRepositoryWithRemove. And now let's inherit from GenericRepository. I press Escape to close IntelliSense and Ctrl+. to
add a using directive for the entity's namespace that contains the Employee class. In this repository, let's create here the Remove method, and let's say this one is here to remove an employee from the repository. Internally, let's use here the items list from the base class, and on that one, let's remove the employee.
Now we have created here a class that inherits from the generic class GenericRepository. Instead of creating a subclass, you can also create a generic subclass because maybe you need the Remove method also on other types. So, let's change this name here to GenericRepositoryWithRemove, and let's define here a generic type parameter. Now you pass this generic type parameter to the base class.
So when this subclass is used with a concrete type, like an employee, that type is also passed to the base class. Now as you can see, the employee here in the Remove method is underlined. When you hover it, you can see here the error cannot convert from Employee to T. Because now as we pass T to the base class, the
base class's items list is now a list of T, and so the Remove method takes a T and not an employee.
This means instead of taking here an Employee, let's take an item of type T, and let's call the parameter item. I press Ctrl+. to rename it. Now everything looks good. Let's go to the Programs.cs file, and here instead of using the GenericRepository, you can now use the GenericRepositoryWithRemove.
This means on this employeeRepository, you find now also this Remove method that takes an employee. Next, you will learn how to use multiple generic type parameters on your generic classes. In this demonstration, you will learn how to use multiple generic type parameters on your generic classes.
So far, we have just defined a single generic type parameter on our generic classes. But now, let's say you want to create in this GenericRepository class a new property that is called Key that you use to identify the GenericRepository instance at runtime. Maybe here, you don't want to hard code the type int. You want to allow Key to be anything that can be specified when the Repository class is used, so you need a second generic type parameter to do this. To define a second type parameter here, you add a comma and then the name of the second type parameter, like, for example, TKey. You can add any number of generic type parameters, and you separate them with a comma. Now, here, you can make the Key property of type TKEY.
As you can see, the Key property gets now underlined in green because we have turned on nullable reference types. When you hover it, you can see the warning that the property 'Key' must contain a value when exiting constructor. Consider declaring the property as nullable. So let's declare the property as nullable by adding here a question mark after the type TKey. Now, this class has two generic type parameters.
Let's scroll down to our subclass. Here, this does not work with a single generic type parameter. So, also in the subclass, let's define a second generic type parameter. Note that the type parameter in the subclass can have a different name. I recommend you to use the same name as in the base class. But here, for example, you could say, TSuperKey, and then you pass the type TSuperKey to the base class. So this means now, when this GenericRepositoryWithRemove class is created, these two types here are passed to the base class.
But now, instead of calling it TSuperKey, let's call it just TKey. Now, you see, we have inherited here from that base class.
I recommend you that if you have more than one generic type parameter that you give them more speaking names. So here, instead of calling the first type parameter just T, you could call it, for example, TItem to make it more obvious what it is. But let's keep here just T for the first type parameter as we will remove the second type parameter at the end of this chapter. Now, let's go to the Program.cs file, and let's see how this works. Here, you see the GenericRepositoryWithRemove class, and it's underlined in red because you just pass in here a single generic type argument, but you need to pass in the second one for that Key property.
So let's say the second one is here int, and now you can see this employee repository has now a Key property that is of type int. Also, the GenericRepository down here needs also the second type argument. Let's use here, for example, a GUID, and now you see the organizationRepository has a Key property that is of type Guid. Now, let me show you one more thing.
Let's go back to the GenericRepository C# file. In a subclass, you can also define less or more generic type parameters than in a base class. So for example, you could add here a TSecret type parameter that you use here in the subclass, but that you don't pass to the base class. What you can also do is pass in here concrete types to the base class. So, for example, instead of TKey, you can use here the string, and then on this GenericRepositoryWithRemove class, you don't need that additional TKey type parameter anymore. This means, now the GenericRepositoryWithRemove class will always have a Key property that is of type string. So when you go back to the Program.cs file, you see for the GenericRepository, you still need two type arguments, but here, for the GenericRepositoryWithRemove class, you only need one type argument. And now, when you look at this
class, it has a Key property, and this Key property is of type string because that's the type that is passed directly to the base class.
Now, let me change this back to just the GenericRepository, and let's go back to the GenericRepository C# file. Here, I just cut the Remove method, and we don't need actually a subclass. So let's delete this subclass. It was just here to explain you a few bits about inheritance. Now, in the GenericRepository class, let's paste here the Remove method. Also, in this class, let's remove the Key property, and let's remove also the second type parameter, TKey. We just need the T parameter. Now, as we don't have a subclass, we can make this items field again private. Now we have this GenericRepository
class with an Add method, with a Remove method, and with a Save method. Let's just run our program to see if everything still works.
And here, you see the error, Using the generic type 'GenericRepository' requires 1 type arguments. Let's this, and now we are here in the Program.cs file, and you can see, here, I use the Repository still with two type arguments.
Let's change this back to one type argument, and let's also remove here the empty line. Now, let's run the application again, and it should build and run. Here, you see the employees, and the organizations. So far, our GenericRepository of T class has the methods Add, Remove, and Save. Now let's implement a GetById method that returns an item by its ID. So, in this demonstration, you will learn how to create the GetById method. And to implement it in our generic class, you will learn how to add the generic type constraint.
Here I've opened the GenericRepository class. Now let's create here a new public method that returns an item of type T, and let's call it GetById. In this method, there's a parameter of type int that I call here id. In this method, let's write here a return statement, and let's use our items list, and on that list, I call the Single method. I press Ctrl+. to add a using directive for the System.Linq namespace that contains the single extension method. Now to this method, let's pass in a function where we get the item, and now on that item, I want to check the id. But as you can see, there is no id property. The item has just the methods that are available on the object type. When you hover the item, you can see it is of type T, and the type T can be any type when this generic repository of T is used.
So you see here just the methods of the object type. But what I want to do here is to grab the Id property and to compare it with the Id that comes in as a parameter into that method. But as you can see, I cannot access here this Id property. I have only the members of the object type. Now let's change this. Let's go to the Solution Explorer, and let's open here the Employee C# file. As you can see, this class has an Id property, and when I open the Organization class, you can see it has also an Id property. This means you can create a base class that contains that Id property. So let me cut the Id property from here, and let's create directly in this file a new class that I call EntityBase, and in that class, I specify the Id property. Now let's inherit the Organization class from EntityBase so it has again the Id property that comes now from the base class.
I put the cursor on the EntityBase class, and I press Ctrl+. to move this type to an EntityBase C# file. Let's hit Enter, and now when you look at the Solution Explorer, you see here this EntityBase C# file. Now let's go to the Employee class, and let's also remove from this class the Id property, and let's inherit this class from EntityBase. Now let's go back to the GenericRepository of T class. And now to get the ID property here, we need to say that T must be of type EntityBase. And to do this, you add the generic type constraint. You define such a type constraint after the class name with the keyword where. So here you say where T, and now you use the same syntax that you use with inheritance.
You use a colon, and you can say T must be of type EntityBase. When I scroll up in this file, you can see I have here already a using directive for the entity's namespace, so EntityBase is found. Now in the code, it's clear that T is of type EntityBase, so when I write here a dot, you see now we get here this Id property because this one is defined in the EntityBase class. So I can use it here, and now we have this GetById method implemented in this generic class.
Let me press Ctrl+K+D here to format this document, and now let's also set the Id property of an item when it is added to the repository. So when an item is added, you can access on that item now also the Id property, as we have defined here a generic type constraint. Let's set the Id property to items.Count + 1. So the first item gets the Id 1. When you remove and add items, this approach could lead to duplicated Ids. If you want to implement a more robust approach, you could grab the max Id from the items list and add 1 to that max Id. But let's keep it simple here, and let's go with this Count + 1 approach, which is sufficient for our needs. Now let's use this GetById method in the Program.cs file.
First, I select here these three lines with the organizationRepository, and I press Ctrl+. to extract the method. Let's call the method AddOrganizations. I also select here these four lines with the employeeRepository, and I press Crtrl+. to extract the method, and I call it AddEmployees, and I hit Enter to confirm it. After adding employees, let's call here a method GetEmployeeById, and I pass in the employeeRepository to that method. Let's press Ctrl+. to generate this static method in our Program class. Here in this method, let's create an employee variable, and on the employeeRepository, let's call the GetById method that returns an employee.
Let's just grab the employee with Id 2. In the Add method of the repository, we set the Id property of an employee, and the employee with Id 2 should be Anna. Now let's do a Console.WriteLine by using the cw snippet, and I write to the console Employee with Id 2, and then we just grab the employee's first name. Now let's run the application to see if Anna is printed to the console. Yes, here you can see employee with Id 2 Anna. So our GetById method is now implemented, and it was possible because we have added here a generic type constraint to this generic repository of T class. This type
constraint allows us to access here the Id property that is defined in the EntityBase class. This means now also that you cannot use the GenericRepository class with other types that are not of type EntityBase. So when you use here, for example, the type object, you get here an error that says the type 'object' cannot be used as a type parameter 'T' in the generic type or method 'GenericRepository.'. There is no implicit conversion from object to EntityBase. So let's press Ctrl+Z to change this back to Employee. In the next chapter, you will learn about the class constraint.
Chapter 7 How to Work with Class Constraint
In this demonstration, you will learn how to work with a class constraint. Previously, you have created here this type constraint on our generic repository of T class so that we can access the Id property on our items of type T. Instead of depending on a concrete class like here, you can also depend on an interface.
This makes the GenericRepository class loosely coupled and usable with more types. So let's create an interface with this Id property. To do this, I go to the Solution Explorer, and I open here the EntityBase C# file. Let's put the cursor on the class name, and let's press Ctrl+., and you can see Visual Studio suggests here to extract an
interface. Let's press Enter, and in this dialog you can define a name for the interface. Let's call it just IEntity.
Let's call the file name IEntity.cs, and we need to add the Id property to the interface. I click OK to confirm this dialog, and now you see the EntityBase class implements this IEntity interface. When you go to the Solution Explorer, you can see now here this new IEntity C# file that contains the IEntity interface with the Id property. This means now, in the GenericRepository class, you can define that interface in addition, or you can only use the interface here because this interface defines the Id property that is needed. So when you hover here the Id property, you can see it comes from that IEntity interface. Now, there is an interesting thing. When you define here a base class like EntityBase, it is clear that T must be of this type, and so it is a reference type. But if you don't define this class, T must just be an IEntity, and it can be a value type or a reference type.
This means now, for example, when you return from this GetById method null, you can see that null is underlined in red. When you hover it, you can see Cannot convert null to type parameter 'T' because it could be a value type. Consider using 'default(T)' instead. So here you can use default(T), and you can also omit the T as it's clear here from the return value. This is underlined in green as it can be null for reference types, so we must mark the return value here as nullable. Now this default key right here will create 0 if T is a numeric type, like int or double, and it will create null if T is a reference type. You could also write explicitly null here.
Now you see it's again underlined in red, and now you can specify here the class constraint which says that T is a reference type, so it can actually be null. Also important to know is how the class constraint behaves when you have turned on nullable reference types like we have for our project. So when you go to the Solution Explorer, and when you here the project to open the csproj file, you can see here that we have enabled Nullable reference types.
This means now that T is, by default, not nullable. Let's see this in action. Let's go to the Programs.cs file, and let's try to use here the GenericRepository with a nullable Employee. As you can see, it gets underlined in green because you get here a warning. When you hover it, you can see that the type Employee cannot be used as a type parameter 'T' in the generic type 'GenericRepository'. Nullability of type argument does not match 'class' constraint. The same warning exists also for the IEntity constraint. So, let's go back to the GenericRepository. You can also add here a question mark to the class constraint to say that T can be a nullable reference type. In addition, you also add the question mark to the IEntity interface to say that it can be a nullable IEntity. Now, when you look again at the Program.cs file, you can see that this nullable Employee reference type is now okay as a type argument for the GenericRepository class.
Now let's remove here the question mark again, let's save this file, and let's close it. When you look at the item in the Add method,
you can also see that it is underlined in green because the compiler warns you here that item can be null because you have marked T as a nullable reference type. But you don't want to store null values in the GenericRepository, so let's remove here the question marks from the IEntity constraint and from the class constraint.
Now T is a reference type, and you can see the item parameter here is not underlined in green anymore. Beside the class constraint, you can also define here the struct constraint to say that T must be a value type, and you can also define other base classes like, for example, System.Enum to say that T is an enum. But now let's remove this, and let's say, in our case, T should be a class.
Now let's remove here this return null from the GetById method, and let's uncomment here our Single call, and the return value cannot be null anymore.
So far to the class constraint. In the next chapter, you will learn about the new constraint that allows you to call a parameterless constructor.
Chapter 8 How to Use the New() Constraint
In this demonstration you will learn how to use the constraint to call the parameterless constructor of your generic type parameter. Here, I've opened the GenericRepository class. Let's create in this GenericRepository class, a method that I call CreateItem, and from that method, let's return the new instance of type T. As you can see, this code line is underlined in red. When you hover over it, you can see here the error, Cannot create an instance of the variable type "T" because it does not have the new() constraint.
To call the constructor, you must ensure that the type T has actually a parameterless constructor. And to ensure this, you will add the new() constraint. This constraint must be the last constraint that you define here. So the order works like normal inheritance in C#. You define here the class name like Entity base, or you use the keywords class or struct. Then you define all the interfaces that you need on
your generic type parameter, and at the end, you can define the new() constraint if you need to create instances of type T by calling the parameterless constructor. Now with this new() constraint and this CreateItem method, this means now that in the Program.cs file on that GenericRepository you have now this CreateItem method that returns the new employee instance.
Now let's see what happens when you add a constructor with the parameter to the Employee class. So let's open here the Employee class, and let's create here a constructor with the ctor snippet. I press the Tab key twice, and let's add on that constructor the firstName parameter. Now let's go back to the Program.cs file. As you can see, the Employee is underlined in red. When you hover over it, you can see the error that the employee must be a type with the public parameterless constructor in order to use it as a parameter "T" in the generic type or method GenericRepository.
When you go back to the GenericRepository class, and when you remove here this new() constraint, it means you cannot implement here the CreateItem method, because no parameterless constructor exists, so let's remove that one, and now let's go back to the Program.cs file, and now you see everything is fine again, even with that constructor in the Employee class. Let's remove here this empty line, and let's remove again the constructor here from the Employee class; we don't need that one. Now let's go back to the GenericRepository class, and let me show you one more thing. When you have multiple type parameters, let's add here another one with the name TKey, then you can define multiple wheres for the different type parameters.
So here, I could say for example, that TKey needs to be a struct. You can even do more advanced things. You can say, for example even that TKey needs to be the subtype of T. So you see, you can play around with these generic type constraints to ensure that the generic type arguments that are passed to your class have the shape that you need.
Now in our case, you just need to ensure that T is of type IEntity, as we need the Id property in our class. So let's remove the rest here, and let's also remove that second generic type parameter. Now let's run the application and let's see if everything still works. We have here the three employees, we have here the employee with Id 2, and then we have here the two organizations. Now let's wrap up what you have learned in this chapter. You learned in this chapter
how to implement a generic class. I showed you how to inherit a class from a generic class, and you learned how to inherit a generic class from a generic class. You also learned how to use multiple type parameters on a generic class. After that, I showed you how to add generic type constraints to a generic class. Type constraints allow you to ensure that a generic type parameter has a specific shape. You learned how to ensure that the type parameter has the type of a concrete class, which was in this chapter the EntityBase class with its ID property. You also learned how to work with the class and struct constraints to ensure that the type parameter is either a reference type or a value type. I also showed you how to call the constructor of the generic type parameter with a new constraint. Besides this, you also learned about the default keyword that allows you to initialize the variable of type T with a default value. The default value is null for reference types and, for example, 0 for numeric types. Now you know how to implement generic classes. In the next chapter, you will continue to work on the console application for a Company. You will implement another generic repository that puts employees and organizations not in a list, but in a database. To make the existing code of our console application also usable with that new generic repository class, you will learn how to work with generic interfaces.
Chapter 9 How to Work with Generic Interfaces
In this chapter, you will learn how to write loosely coupled code by using generic interfaces. First, I will ensure that you understand why you want to use a generic interface. After you know this, you will learn how to create a generic interfere and also how to use a generic interfere. I will also ensure in this chapter that you understand what covariance is and how it helps you when working with generic interfaces. I will also ensure that you understand what contravariance is. Finally, you will learn how to work with interface inheritance when using generic interfaces. Now let's ensure that you understand why you want to use a generic interface. Previously, you created the StorageApp, which is a .NET console application. In this application, you created the GenericRepository class that stores employees into organizations in a List. In this chapter, you will continue to work on this .NET application. The first thing that you will do is renaming this GenericRepository class to ListRepository to make it more obvious what it does. Then you will implement another generic class that is called SqlRepository. In this class, you will use Entity Framework Core to store the items in a database. But I don't want you to have to set up a database in this book. This is the reason why you will use an database with the detail provider for Entity Framework Core. That data provider is meant to be used only for testing as it lacks real database features, like, for example, referential integrity. But as it is very simple to set up and as it is also sufficient for our needs, we will use it. Important to know is
that the code that you will write in the SqlRepository class is exactly the same code as the code that you would write to access a real database like SQLite or SQL Server. So now you see we have two different repository implementations, but when you look at the Program class of our .NET console application, you can see the methods like AddEmployees and GetEmployeeById. These methods have a ListRepository parameter. This means that you cannot pass a SqlRepository instance to these methods. But a Company wants you, as the developer, to make these methods in the Program class work with list repositories and also with SQL repositories. To make this possible, you will introduce the generic IRepository interface that will be implemented by the ListRepository class and also by the SqlRepository class. After introducing this interface, you can change the parameter types of the methods in the Program class from ListRepository to IRepository. This means that these methods will work with any IRepository implementation, like ListRepository or SqlRepository. So, the methods depend now on the generic IRepository interface, which is an abstraction, and they don't depend on a concrete implementation. This is actually a software principle that we used here, and it is called the dependency inversion principle. It says that components, like our static methods in the Program class, must depend on abstractions and not on implementations. And that's exactly what we do. The methods in the Program class depends on the IRepository interface, which is an abstraction. now let's implement this. Before you will create this IRepository interface, you will build the generic SqlRepository class. In this demonstration, you will learn how to build a generic SqlRepository class. In this class you will use Entity Framework Core, and you will store the data in an database. Here I've opened the StorageApp project that you have created Previously. Let's select here the GenericRepository.cs file, and let's rename that file to
ListRepository. I press Enter and Visual Studio asks me here if I also want to rename the code element GenericRepository. Yes, I want to do this.
Now you can see here the class was also renamed to ListRepository. Now in the C# file let's collapse this class. Let's select it. I press Ctrl+C to copy it, and I paste it again into this file. Now let's scroll up and let's call the new class not ListRepository but SqlRepository. Now I press Ctrl+., and you can see Visual Studio suggests here to move the type to a SqlRepository.cs file. Let's press Enter to do this. Let's go back to the Solution Explorer, and now you see here this new SqlRepository.cs file that contains the SqlRepository class.
The Single method here is not found because the System.Linq namespace is missing. Let's put the cursor on this method and let's press Ctrl+. to add using for System.Linq. Instead of storing the items here in a List, you should store them in an database with Entity Framework Core. This means you have to set up an Entity Framework DbContext. Let's do this in our project. I here the project, and I select from the context menu Add, New Folder. Let's call the new folder Data. Now I this folder, and I select from the context menu Add, Class. Let's give this class the name StorageAppDbContext. This will be our Entity Framework Core DbContext. Let's add it to the Data folder. Now we need Entity Framework Core, so let's on Dependencies, and let's select here Manage NuGet Packages.
As as you can see, I have selected here already Browse, so let's search here for EntityFrameworkCore.InMemory, and let's select here the Microsoft.EntityFrameworkCore.InMemory NuGet package, and let's click here on the Install button. A preview with the changes comes up, I click OK in this dialog, and I also accept here the license. This EntityFrameworkCore.InMemory NuGet package is installed, so let's close this tab. You can also open the cs.proj file by the project in the Solution Explorer, and then you'll see here the package reference that you could also add manually in the cs.proj file. Now let's close the cs.proj file.
Let's inherit the StorageAppDbContext class from DbContext. I press Esc to close IntelliSense and Ctrl+. to add a using for Microsoft Entity Framework Core. Now, let's also make this class a public class, and let's put the cursor on the usings here, and I press Ctrl+. to remove the unnecessary usings. Now, in this class we need a property for the Employees. In Entity Framework Core, this is a DbSet. As you can see, this is a generic class that takes the entity type as a generic type argument. So let's create here a DbSet. I press Cntrl+. to add a using for the Entities namespace that contains the Employee class. Now let's call this property Employees. As you can see, it is underlined in green because we have enabled nullable reference types in the cs.proj file. When you hover it, you can see here the warning that the property Employees must contain a value when exiting constructor. Consider declaring the property as nullable. So you could declare here the property with a question mark as nullable, and then the warning disappears. But on a DbContext, the DbSet property is usually not null, as it is initialized in the constructor of the DbContext base class.
Instead of using here the question mark, you can do a different thing. You can use here an expression body, and you can use the generic Set method that is defined in the DbContext base class, and
you can see this method returns a DbSet of TEntity. To use this generic Set method, you have to pass in a generic type argument. In this case, let's pass in the type Employee, and then this Set method will return a DbSet exactly like we needed for this property.
I copy this DbSet property, and we create also one for the organizations. So here let's call the property Organizations, and let's called this generic Set method with the Organization type. Now besides these two properties, let's override here the OnConfiguring method. On the optionsBuilder, you'll find an extension method that is called UseInMemoryDatabase. You have to specify a database name with this method. Let's call the database StorageAppDb. Now this Entity Framework DbContext is set up, and you can implement the SqlRepository class, so let's open this class. First, let me remove here this _items field, as we don't need that list.
Next, let's create a constructor, and the constructor takes as a parameter a DbContext instance. I press Ctrl+. to add a using for Microsoft Entity Framework Core. Let's call the parameter dbContext, and I press Ctrl+. to create and assign a field _dbContext. In addition, I also want to grab the DbSet from the dbContext, and I want to store that in an _dbSet field. So on the dbContext, I use the generic Set method that we have used already in the context, and as an entity, I specify the type T. Now I press Ctrl+. to create that DbSet field in our class. Here is the created dbSet field, but as you can see it is underlined in red. When you hover it, you see here the error that the type T must be a reference type in order to use it as a parameter TEntity in the generic type or method DbSet.
This means here we need to adjust the generic type constraint, as T must be a reference type so that it can be used with Entity Framework Core. To do this, let's use here the class constraint before the IEntity interface. Now you see the error is gone. So next, you can implement now the methods. GetById is a very simple one. On the dbSet, you can call the Find method with the id, and this will return the object by its ID. The Add method is also quite simple. On the dbSet you call the Add method with the item of type T.
The Remove method is also simple. On the dbSet you call the Remove method with the item. In the Save method you don't need a for each loop. Instead, you use the dbContext instance, and you call it's SaveChanges method. that's it. Now you have implemented a generic SqlRepository class that uses an Entity Framework DbContext to store the items. Now let's go to the Solution Explorer and let's open the program.cs file, and here instead of a ListRepository, let's use the SqlRepository.
The constructor of the SqlRepository takes a DbContext instance, so let's create here a new instance of our StorageAppDbContext class. I press Ctrl+. to add a using for the Data namespace that contains this class. So now you have here a SqlRepository instance, but as you can see it is underlined here in red, as it is not a valid argument for these two methods. This is because these methods take ListRepositories and not SqlRepositories. So when you hover here the employeeRepository argument, you can see the error cannot convert SqlRepository to ListRepository. Let's fix this in the next chapter by creating a generic interface.
Chapter 10 How to Create Generic Interface
In this demonstration, you will learn how to create a generic interface. Here, I've opened the Program.cs file of the StorageApp. Previously, you have created the SqlRepository class, but it does not work with these methods, as these methods have a ListRepository parameter. Now let's introduce a generic IRepository interface so that these methods work not only with list repositories, but also with SQL repositories. To do this, let's open here the SqlRepository class. First, let me put the cursor on the usings, and let's press Ctrl+dot to remove unnecessary usings. Now, let's put the cursor on the SqlRepository class name, and let's press Ctrl+dot, and you can see Visual Studio suggests here to extract an interface.
Let's press Enter to select this option. In this dialog, let's call the new interface just IRepository, and I want to create it in the new file
name IRepository.cs. Next, you can select here the public members to form the interface. I keep all members selected, and I click the OK button. Now you can see the SqlRepository class implements this generic IRepository interface. Let's go to the Solution Explorer, and you can see here the IRepository C# file. Let's open the C# file until you see the generic IRepository interface. Like a generic class, it has here a generic type parameter and also a type constraint for the type parameter. Then the type parameter is used for the methods that are defined in this interface. When you define type constraints like here, it means that generic classes that implement this interface also must define these type constraints for their generic type parameter. Let me show you this by implementing the interface now on the ListRepository class. Let's expand this class, and here, let's implement the new IRepository interface.
As you can see, the ListRepository class is underlined in red. When I hover it, you can see that The type 'T' must be a reference type in order to use it as a parameter 'T' in the generic type or method 'IRepository'. This means also here in this class we need now this class type constraint, but maybe this makes not so much sense, as this is an implementation detail of this ListRepository class that is
not needed. So let's keep it like this. Let's go back to the IRepository interface, and let's remove from this interface the class constraint.
Now when you go back to the ListRepository, you can see this class is now fine with just the IEntity constraint. Alright, so now the interface is implemented on the ListRepository and also on the SqlRepository class. Now let's go back to the IRepository interface, as I want to mention one more thing. The IEntity type constraint here on the interface is also not necessary, as there is no implementation in this interface that needs that type constraint. So, you might ask yourself why having type constraints on interfaces as interfaces usually never have an implementation. Keeping this IEntity type constraint here on the IRepository interface means that you will get a compile error if you use this IRepository interface with a generic type argument that is not of type IEntity. I think this makes sense for this IRepository interface, especially as we have here this GetById method. That makes only sense with IEntity instances that have an ID property.
So, let's keep this IEntity constraint here, which makes the IRepository interface only usable with types that implement the IEntity interface. Now as this IRepository interface is implemented by the classes ListRepository and SqlRepository, you can use it in the Program class. So let's navigate to the Program.cs file. Down here where we have the GetEmployeeById method, let's change now the parameter type to IRepository. As you can see, the error here is gone. Let's also change the parameter type of the AddEmployees method to IRepository, and you see here the error is gone. Let's scroll down, and let's also change the parameter type of this AddOrganizations method to IRepository.
Now we have introduced this generic interface, and it means all the static methods in the Program class can be used with either SQL repositories or list repositories or any other IRepository implementation. Let's run the application to see if our code still works.
Yes, here you can see the Employee with Id 2, which is Anna. This employee is read from the SqlRepository, respectively from the database.
Next, you see here the two organizations from the ListRepository. As you can see, all employees are not printed to the console, as the SqlRepository does not do this in its Save method. Next, you will add a GetAll method to the IRepository interface that you will use in the Program class to read all employees and all organizations so that you can write them to the console. Previously, you created the generic IRepository interface. You can call its GetById method to get a single item by its Id. Now, you will add the GetAll method to the IRepository interface that returns all items. As you can see, the return type is IEnumerable. IEnumerable is an existing generic interface in .NET from the namespace System.Collections.Generic. You can iterate through the items of an IEnumerable instance with a foreach loop. The interface is implemented by generic collections like List or Stack and also by all C# arrays. So, you see, it's a central interface that represents a sequence of items. Also, all LINQ methods are extension methods of this generic interface, which means, you can run queries in C# against an IEnumerable instance.
Now, in this demonstration, you will learn how to add and implement the GetAll method in the Repository classes of our project, and you will learn how to use the existing IEnumerable interface of .NET. Here, in the.StorageApp project, let's open the IRepository interface. First, let me select here the Add method, I cut it, and I paste it below the GetById method. The order of the methods does not matter, but I like it this way because GetById is a method to read from the repository and add and remove our methods to write to the repository.
Now, let's add here a GetAll method, and let's say that the return type is IEnumerable. I press Ctrl+dot to add a using for the System.Collections.Generic namespace that contains this IEnumerable interface. Now, we have this new GetAll method that you can use to get all the items from the repository. As you implement this interface in the ListRepository and also in the SqlRepository, you have to add this GetAll method now in these implementations. Let's start with the ListRepository. As you can see, the interface is underlined in red here. And when you hover it, you can see, 'ListRepository' does not implement interface member 'IRepository.GetAll()'. Let's put the cursor on the interface, and I press Ctrl+dot, and here, you can select the
option Implement interface. This option adds the missing GetAll method to the ListRepository class. You can see it here at the bottom.
I cut it from here, and I paste it also at the top of this class. Now, from this method, you can return the list that we have stored in the items field as this list implements the IEnumerable interface. But returning the list directly means that a user of the repository gets access to this internal storage. Instead of returning the list directly, let's return a copy. You can do this by calling here the ToList method, which is an extension method from the System.Linq namespace that I have already included here. Now, let's just copy here this GetAll method, and let's go to the Solution Explorer, and I open now the SqlRepository class. In this class, I just paste this method. In this case, you don't have the items field. Instead, you'll use here the dbSet. I put the cursor on the ToList method, and I press Ctrl+dot to add a using directive for System.Linq. I also put the cursor on the IEnumerable interface, and I press Ctrl+dot to add a using directive for the System.Collections.Generic namespace.
Now, you have also implemented this GetAll method of the IRepository of T interface in the SqlRepository class. Now, let's use this GetAll method in our application. So let's open here the Program.cs file. Here, after the call of the GetEmployeeById method, let's call a new method with the name WriteAllToConsole. And as an argument, let's pass in the employeeRepository. I press Ctrl+dot to generate this method in the Program class. Let's change the parameter type from SqlRepository to IRepository so that it can be used also with the ListRepository of Employee. Now, in this method, let's grab all employees from the repository.
So let's create here an items variable, and on the employeeRepository, I call that GetAll method. And you can see, it returns an IEnumerable of Employee. Now, you have the IEnumerable of Employee instance in the items variable, and you can create a foreach loop to iterate through the items. I press the Tab key twice to insert the foreach snippet, and I say, foreach item in items. And inside the foreach loop, let's create a Console.WriteLine statement, and let's write the item to the console.
This means now, after we print out here an Employee by Id, you should see all employees written to the console. Let's see this in action, and let's run the application. Here, you see the Employee with Id 2, which is Anna, and then you see all employees of the SqlRepository that we grabbed with the GetAll method.
In the next chapter, let me show you how you can use the WriteAllToConsole method also for the organizations.
Chapter 11 Covariance Basics
In this demonstration, I will ensure that you understand covariance, which is important to know when you create generic interfaces. Here, I've opened the Program.cs file of the StorageApp. Previously, we have created this WriteAllToConsole method that has an IRepository of Employee parameter. It calls the GetAll method on the repository, and it writes all the items to the console.
Now, let's say you want to use this method also with organizations. So here, after adding organizations to the ListRepository, let's call this WriteAllToConsole method with the organizationRepository. The argument is underlined in red. And when you hover it, you can see the error cannot convert from ListRepository of Organization to IRepository of Employee. But as you know, the Organization class and also the Employee class implement the IEntity interface. So instead of taking here an IRepository of Employee, you could take
here an IRepository of IEntity so that it works with employees and also with organizations. Let's call the parameter just repository. I press Ctrl+dot to rename it from employeeRepository to repository.
But now, you see that this method neither works with an employeeRepository, nor with an organizationRepository. The reason for this is that parameters of interfaces are, by default, invariant. This means they have to have exactly the same type as the type that you used on the class that implements the interface. What does this exactly mean? It means, if you have, for example, an IRepository of Organization variable, let's call it just repo, and you assign the ListRepository of Organization, you see that you have on the interface exactly the same type argument as on the class. This works, of course, as the ListRepository of Organization is an IRepository of Organization. But the generic type parameter of this generic IRepository interface is, by default, invariant.
This means, on the interface, you cannot use a less specific generic type argument like, for example, the IEntity interface. When you specify this, you can see that the ListRepository class gets underlined in red. And when you hover it, you can see, Cannot implicitly convert type ListRepository of Organization to IRepository of IEntity. But actually, when you look at this repo variable, it would be okay to have a GetAll method that returns an IEnumerable of IEntity as the implementation returns a more specific type. So you could still cast the IEntities to organizations. Also, for the GetById method, it would be okay to return this less specific type. But for methods where you pass in an item, it's not okay to pass in an IEntity because the ListRepository needs, actually, an Organization. This means, for the methods where you read from the repository, you could have a less specific type, and this is what covariance is about.
So let's keep this line here for now, and let's go to the IRepository interface. As you can see, here, we have the two methods that return
the type T, and we could use a less specific type here. But for the methods Add and Remove that take a type T as input, you can't have a less specific type. As I mentioned, by default, the generic type parameter is invariant. You can define it as covariant by using the out keyword before the generic type parameter. As you can see, defining the generic type parameter as covariant with the out keyword is okay for the methods where the type goes out, respectively, is returned, but it is not okay for the methods where the type goes in. When you hover here, the T, the error says, Invalid variance: The type parameter 'T' must be contravariantly valid on 'IRepository.Add(T).' 'T' is covariant.
So this means, in other words, you cannot define the generic type parameter on this interface as covariant with the out keyword, but what you can do is you can define a separate interface that contains just these two methods that return the type T. Let's do this directly in this file. I create here a public interface, and I call it IReadRepository, and I just grab here the two methods, GetAll and GetById, I cut them, and I paste them into this new interface. Now, I inherit the IRepository interface from this IReadRepository interface of T so that this interface has also the methods GetAll and GetById. Now, let's go back to the Program.cs file. So far, nothing has
changed. But I can use here, for example, this IReadRepository, but we have still the same error as this interface also has an invariant type parameter. Now, let's change this.
Let's go to the IRepository C# file, and now let's add on this interface the out keyword to the type parameter. This makes now the type parameter covariant, which means a less specific type can be used. When you go back to the Program.cs file, you see now, this line doesn't have an error anymore.
It is okay to assign a ListRepository of Organization to an IReadRepository of IEntity. As we have no generic type constraint on this IReadRepository interface, you could even say here that the type
is object, so this works as well. This means, now, let's remove this line. You can use in this WriteAllToConsole method the IReadRepository interface, and now you see, the errors here disappeared, and you can pass in any IReadRepository of IEntity implementation.
Now, let's run the application, and you should see all employees printed to the console and also all organizations. Let's stop the application again, and let's adjust the ListRepository class. Let's open this class, and let's scroll down to the safe method, and let's remove here the for each loop that writes the items to the console. I add a comment here, Everything is saved already in the list of T. Alright, so nothing has to be done in this safe method.
Now, we should just see the output of the WriteAllToConsole method of our Program class. You'll see all the employees, and here, you'll see all the organizations. Now you learned about covariance that allows you to use a less specific generic type argument for the generic interface. In the next chapter, you will learn about contravariance.
Chapter 12 Contravariance Fundamentals
In this demonstration, I will ensure that you understand contravariance. Here in the StorageApp project, let's open the Employee class, and let's create a subclass from this class with the name, Manager. So I inherit this one from Employee. In this subclass, let's override the ToString method. I also use here an expression body, and I call the ToString method of the base class, so this one here of the Employee class, and I just add the Manager suffix to that ToString result.
Now, we have a more specific class that inherits from Employee. Let's put the cursor on the Manager class name, and I press Ctrl + dot to move this type to a Manager C# file. Let's hit Enter. Now let's go back to the Solution Explorer. Here, you see the new Manager C# file. Now let's open the Program.cs file. In the Main method, after calling here the AddEmployees method, let's call a new
method with the name AddManagers. I pass in as an argument the employeeRepository. Now you can press, again, Ctrl + dot to generate this AddManagers method in the Program class. Here, you see it. First, let me change the parameter type from SqlRepository to IRepository so that this method would also work with a list repository of Employee. Now, in this method, let's add two managers to the repository. So let's call the Add method, and let's pass in here a manager with the first name Sara. I press Ctrl + C to copy this line, and I paste it again.
And let's create another manager with the first name Henry. After this, let's call on the employeeRepository the Save method. But as you might have noticed, the employeeRepository.Add method takes an Employee, so passing in an Employee instead of a Manager is also valid here in this AddManagers method. But now, maybe inside of this AddManagers method, you only want to allow to add managers. To do this, you could change here the generic type argument on this IRepository interface from Employee to Manager. Now you see Employee is not a valid option anymore for the Add method. When you hover it, you see here the error, cannot convert from Employee
to Manager. Now, as we have changed here the interface, the Add method here takes only managers, but not employees anymore.
Let's rename the parameter here from employeeRepository to managerRepository. I press Ctrl + RR, and I type in managerRepository, and I hit the Enter key to apply the changes. Alright, now inside this method, you can only add managers to the repository. That's great. But when you scroll up, you'll see here an error where you pass the SqlRepository of Employee to the AddManagers method. When you hover here, the argument, you'll see the error, cannot convert from SqlRepository of Employee to IRepository of Manager.
Yes, you know that the generic type parameter of this IRepository interface is invariant. I will show you now what this means. Let me copy here the line with a SqlRepository, and let's paste it here again, and let's call the variable just repo. Now let's explicitly write here the IRepository interface of Employee. This is fine as we have here exactly the same type argument as here on the other side, which is an IRepository of Employee implementation. But now, if you use here for the interface Manager, this does not work anymore. When you hover the SqlRepository, you see here the error, Cannot implicitly convert type SqlRepository of Employee to IRepository of Manager. This is because Manager is a more specific type than Employee, but you can support this by defining the generic type parameter on the IRepository interface as contravariant.
Let's go to the Solution Explorer, and let's open the IRepository C# file. Previously, you have defined here this interface with a covariant type parameter. This means the type can be less specific on the interface. But now with the manager, we need a more specific type
or respectively, a contravariant type. To create a contravariant type parameter, you write the in keyword before the type parameter. This works if the type parameter is not used as a return value in the generic interface. But now, as you can see, T is underlined in red. When you hover it, you see here the error Invalid variance: The type parameter 'T' must be covariantly valid on IReadRepository. T is defined here as contravariant.
Here, IReadRepository needs a covariant type parameter, and here we have defined the type parameter as contravariant. This does not work. It means we have to split up these methods into a separate interface. So let's remove here from the generic type parameter of this IRepository interface the in keyword. Now let's create in this file a new public interface, and let's call it IWriteRepository. And now, I just grab all the methods of this IRepository interface, and I paste them into this new IWriteRepository interface. I have also included here the Save method, as this method belongs also to IWriteRepository.
Now down here, let's inherit this IRepository interface also from IWriteRepository. I add a line break here for the type constraint. Now, before we define this type parameter here as contravariant, let's go back to the Program.cs file. Here, let's define now instead of IRepository an IWriteRepository. As you can see, SqlRepository is still underlined in red. Now let's go again to the IRepository C# file, and let's define here this type parameter as contravariant. Now let's go back to the Program.cs file.
As you can see, here the error from the SqlRepository class disappeared. Now, when you use this repo variable, you have here the Add method that takes a Manager, and you have the Remove method that also takes a Manager. So, when you call the Add method, for example, with a new Manager, that Manager is passed to the SqlRepository's Add method that can handle employees. As a manager is an employee, this works fine.
Now let's remove these code lines again here. Let's continue with our AddManagers method down here. Instead of taking now an IRepository of Manager, let's take here an IWriteRepository of Manager, and now you see the error here is gone. You can pass now a SqlRepository of Employee or also a list repository of Employee to this AddManagers method, and it will just work. But inside this method, you can only add managers, as the managerRepository parameter here has the generic type argument, Manager.
Now let's see this in action, and let's run our application. You see the three employees, and here, you see the two managers. As you can see, the managers are not ordered by their ID.
Now let's change this. I open here the SqlRepository class, and I scroll down to the GetAll method. Here, on the dbSet, before calling the ToList method, let's use the OrderBy method from the System.Linq namespace.
Let's pass in a function that grabs from the item the ID. You can use here the ID, as we have here this generic type constraint with the IEntity interface. This means now that you will get the items ordered by ID. Let's run the application, and now you'll see all the employees and managers are ordered now by their ID. In the next chapter, you will learn more about working with interface inheritance.
Chapter 13 How to Work with Interface Inheritance
In this demonstration, you will learn how to work with interface inheritance. Previouslys, you learned already how to inherit this IRepository interface from these two generic interfaces. So you see, like with class inheritance, you just pass the generic type parameter to the base interfaces. You can also define more generic type parameters on a subinterface, or you can also inherit a interface from a generic interface. Now let me show you a few more things in this file. Let's create here another interface, and let's call it ISuperRepository, and let's inherit that one from IRepository.
As you can see, it is underlined in red, and when you hover it, you see here the error that the type 'T' cannot be used as a type parameter 'T' in the generic type or method IRepository. There is no conversion from T to IEntity. This is because in IRepository, T must
be an IEntity, so you also have to add here this type constraint where T is IEntity. Now, what you can also do, of course, is defining other type parameters on a subinterface. So here, I could create here, for example, a Key property in this interface that is of type TKey. But what you can also do, you can also inherit a interface from a generic interface. So let's remove here the type constraint, and let's say you want to inherit from IRepository.
Then the interface might have the name IEmployeeRepository. So you see, you can inherit generic interfaces from generic interfaces, and you can also inherit interfaces from generic interfaces. Of course, you can also inherit a generic interface from a interface. When you look, for example, at the IEnumerable interface, you will see that. Let's this interface, and let's select here Go To Definition from the context menu. As you can see, this generic interface has a covariant type parameter, and it inherits from the IEnumerable interface. Now let's close this again.
The same concepts apply also to classes. So let's change here the interface to an EmployeeRepository class that implements the generic IRepository interface. When you put the cursor here on this IRepository interface, you can press Ctrl+., and Visual Studio suggests to you here to implement the interface. Let's do this. And now you see you have here an Add method that takes an Employee, a GetAll method that returns an IEnumerable, and so on. Now let me remove the members again from this Employee repository class.
Let's delete them, and now the class is empty again. Instead of implementing the interface on a class, you can also create a GenericSuperRepository class. Let's add a type parameter here, and let's inherit from IRepository. And you need again then the generic type constraint. Now again here, you can put the cursor on the interface, and I press Ctrl+. to implement the interface, and you see now here an Add method that has a T parameter, a GetAll method that returns an IEnumerable, and so on.
You could also define here on the generic class multiple type parameters, if you need them, and then you'll just pass that single one that is needed by the IRepository interface to that interface. now let's remove this class again.
So far to working with inheritance with generic interfaces. Now let's wrap up what you have learned in this chapter. You learned in this chapter how to create and use the generic interface to make the code in our application work with list repositories and also with SQL repositories. You also learned that generic type parameters of generic interfaces are by default invariant. Then you learned how to declare a generic type parameter on a generic interface as covariant. You do this with the out keyword before the type parameter. This works if T is used in the generic interface only for return values of methods and properties. Covariance allows you to use a less specific generic type argument when you use the generic interface. You also learned how to declare a generic type parameter as contravariant. You do this with the in keyword before the type parameter. This works if T is used in the interface only for input parameters. Contravariance allows you to use a more specific generic type argument when you use the generic interface. You also learned how to work with interface inheritance. From a generic interface, you can inherit other generic
and also interfaces, and you can also implement the generic interface on generic classes and also on classes. Now you know how to work with generic interfaces. In the next chapter, you will continue to work on the .NET console application for a Company, and you will learn how to create generic methods and generic delegates.
Chapter 14 How to Create Generic Methods and Delegates
In this chapter, you will learn how to make your methods and delegates reusable by making them generic. First, you will learn how to work with generic methods. You will start with a method and then you will create a generic method to make it reusable with different types. And finally, you will build a generic extension method for a generic interface. In the second part of this chapter, you will learn how to work with generic delegates. You will start with a delegate and then you will create a generic delegate to make it usable with different types. You will also learn how to use the generic Action delegate of .NET, and you will learn how to create events with the generic EventHandler delegate that is also part of .NET. Now let's start and let's add a method to our .NET console application In this demonstration, let's add a method to our console application that provides a new feature. Here I've opened the .StorageApp that you have created Previouslys.
Now let's open the Program.cs file, and let's scroll down in this file to the AddOrganizations method. In this method, you call for each
organization the Add method of the repository. Now let's say you also want to be able to add an array of organizations to the repository. So let's create here a new variable, and let's call it organizations, and let's initialize it with a new array. Now let's move these two organizations into this organizations array, and let's remove here the calls of the Add methods. Now to add the organizations of this array to the repository, let's create a new method that I call here AddBatch. As a first argument, let's pass the organizationRepository to this method, and as a second one, the organizations array. I press Ctrl+. to generate the AddBatch method in the Program class.
In this method, let's create a foreach loop, and let's say for each item in that organizations array, and for each item you want to call on the organizationRepository the Add method that adds the item. After this, you should call the Save method on the repository, like we did it here. So let's cut this statement here with the call of the Save method. I press Ctrl+X, and let's paste it into the AddBatch method.
Now this AddBatch method allows you to add an array of organizations to the repository. Internally, it calls the Add method for every organization. Now let's run the StorageApp, and let's see if this still works. In this demonstration, you will learn how to create a generic method. Previously, you have created this AddBatch method to add an array of organizations to an organization repository. Now let's use this method also for employees. So let's scroll up, and you'll see here the AddEmployees method. So first, let's create here also an employees variable, and let's initialize it with a new array. I put the cursor here before the employeeRepository, and I press down the Shift key and also the Alt key. This allows you to select all these three rows with the Add method, and you can press the delete key. Now let's just cut these three employees, and let's paste them into the array. At the end, I need to remove the semicolons and the parenthesis, and I need to add commas. Let's add a semicolon, and let's indent this a bit, and now this looks good.
Now let's remove here the call of the Save method. And instead of this, let's call the AddBatch method with the employeeRepository and with our employees array. As you can see, this does, of course, not work. When you hover it, you can see, cannot convert from
IRepository of Employee to IRepository of Organization. So, let's scroll down to the AddBatch method. Now, you might think maybe you can use here the IEntity interface as the Organization class and also the Employee class implements this interface. But as you can see, organizationRepository is underlined in red here, and when you hover it, you can see here the error, cannot convert from IRepository of Organization to IRepository of IEntity. The generic type parameter on this IRepository interface is invariant.
On the IReadRepository, we have defined it as covariant, so this here would work, but the IReadRepository does not have the Add method, and it also does not have the Save method. On the IWriteRepository interface, you have the methods Add and Save, but the generic type parameter is defined as contravariant. This means that the generic type argument can be more specific here on the interface, but not less specific. But here, the IEntity interface is less specific than organization. This means you get a compile error when you try to pass like here an IWriteRepository of organization to an IWriteRepository of IEntity. So, you see all these approaches don't work here. Let me press Ctrl+set to get the IRepository interface of organization again. But what you want to do here for the employee, you want to replace here the type organization and also here. You can actually do this by defining this AddBatch method as a generic
method. To do this, you create, after the method name, a generic type parameter.
Now you can use this type parameter for the method parameters, and if you want, also for the return type of the method. So let's replace here Organization with T, and let's replace the Organization array with an array of T. Now let me also rename here organizationRepository to just repository. I press Ctrl+RR, and I write repository, and I hit Enter. Let's also rename the parameter organizations to items. I press again Ctrl+RR, and I enter here items. Now we can also remove the line break, as this parameter is quite short. But as you can see now, the repository is underlined in red. When you hover it, you can see the error, The type 'T' cannot be used as a type parameter 'T' in the generic type or method 'IRepository'.
There is no conversion from T to IEntity. On the IRepository interface, we have a type constraint that says that T must be an IEntity. So you must add the type constraint for this type parameter as well. You can do this on a method also with the where keyword. Here you can write where T implements IEntity. Now you see the error disappeared. But instead of taking here the IRepository interface, you can take the less specific IWriteRepository interface, as this interface has the methods Add and Save. This interface does not have a type constraint, which means you can remove the type constraint also from the generic method. Now when I scroll up, you can see that you can use the AddBatch method for employees and also for organizations. Actually, let me remove this line again. When you call the AddBatch method, you can see that it's a generic method because of these angle brackets here. IntelliSense also shows you that it has one generic type parameter and that it takes an IWriteRepository of T and an array of T.
Now you can say, in this case, you want to use it with the Employee type, then you can see it takes an IWriteRepository of Employee and also an Employee array. This means you can pass in the employeeRepository and the employees array. Now, the C# compiler is smart enough to detect from the method arguments that you want to call the AddBatch method with the type Employee because this is
here, for example, an IWriteRepository of Employee. This means, in this case, the generic type argument is optional, and you can remove it. When you hover the AddBatch method, you'll see that the compiler uses AddBatch of Employee. In the next demonstration, you will learn how to build the generic extension method. Previously, you have created this generic AddBatch method that can add an array of T to an IWriteRepository. Wouldn't it be great if this method would be available on all IWriteRepository instances? You can actually do this by implementing this method as an extension method for this IWriteRepository interface. Let's do this in this now. First, I select the method, and I cut it from here. Now, let's go to the Solution Explorer, let's on Repositories, and let's select from the context menu Add, Class. Let's create here a new class that I call RepositoryExtensions, and let's add this class to this Repositories folder. First, let's press Ctrl+dot to remove the unnecessary usings. Next, I make this class a public static class. Static means that it can contain only static members.
Now, let's paste the static AddBatch method into this class. Let's change the access modifier here from private to public so that it is visible for other classes. So far, this is now just a normal static
method in this RepositoryExtensions class. This means, in the Program.cs file, instead of just calling AddBatch, you have now to call RepositoryExtensions.AddBatch. Let's scroll up, and let's also do this in this AddEmployees method. But now, let's go back to the RepositoryExtensions C# file. To change this method to an extension method, you add the this keyword before the first parameter. This means now that this AddBatch method is an extension method for IWriteRepository instances.
Let me show you this now in the Program.cs file. Instead of using here the RepositoryExtensions class name to call the static AddBatch method, you can call the method now as an extension method on the employeeRepository, as this repository is an IWriteRepository of Employee. So when you write here dot, you see here the extension method, AddBatch, that takes an Employee array. You can see this method extends the IWriteRepository interface. So now, you can just call this method on this employeeRepository object, and you can pass in the employees array. Down here, for the organizations, you can do the same. On the organizationRepository, you'll also find this AddBatch method that takes here an array of organizations. So let's call the AddBatch method, and let's pass in the organizations array.
Chapter 15 How to Write Generic Method with Return Value
In this demonstration, you will learn how to write a generic extension method with return value that you can use to copy entities. Here in our StorageApp project, let's the Entities folder, and let's select from the context menu Add Class. Let's call the class EntityExtensions, and let's add it to that folder. First, let's remove the unnecessary usings, and let's make this class a public static class.
Now in this class, let's create the static copy method. Let's return object for now, and that takes an object parameter, itemToCopy. Now, as you can see already, you can make this a generic method, and then you can replace your object with a generic type parameter. Let's do this. Let's define here a generic type parameter, and let's replace object with the type, T. Let's also change it into an extension method by using before the parameter the this keyword. Inside this method, let's create a JSON string from the object. So let's create here a json
variable, and I use the JsonSerializer class. Let's press Ctrl+dot, and I add a using here for System.Text.Json. The JsonSerializer class has a generic Serialize method. We want to serialize an object of type T, and let's pass in the itemToCopy parameter to that method. To copy the object, we deserialize the JSON again into an object. So, let's return the result from the JsonSerializer's Deserialize method. We want to deserialize the JSON string into an object of type T.
Let's pass in the json string. As you can see, this method is underlined in green, and when you hover it, you see here the warning, Possible null reference return. This is because I have turned on nullable reference types. So let's mark here the return type as nullable, and now you see we don't have a warning here anymore. Note also that here the compiler can infer this generic type argument from this method argument that you passed to the Serialize method. So in this case, the generic type argument is optional. But I keep it here, as it looks nicer in combination with the Deserialize method. Now let's use this extension method in the Program class. Let's open the Program.cs file. I scroll down to the AddManagers method, and I want to copy here this Manager with the FirstName Sara. So first, let's select here the Manager, and now
I press Ctrl+dot, and Visual Studio suggests me here to introduce a local variable for this manager. Let's hit Enter to do this. Let's call the Manager saraManager, and I hit Enter to apply the changes.
Also here on the left side, I'll use the var keyword. Now, after creating the Manager, let's call on that Manager the Copy extension method that you see here. As we have already a manager, the compiler detects already that you want to call Copy that returns a Manager object. So, also here, the generic type argument is optional. You can omit it, as the compiler can infer it from this variable type. So let's create another variable, saraManagercopy, and let's assign the copy to that variable. Now, after adding the proper saraManager, I check here if saraManagerCopy is not null. And if that's the case, I grab from that copy the FirstName property, and I just append the string, Copy. After that, let's call on the managerRepository the Add method to add also that copy to the repository. This looks good, but I want to show you one more thing. When you have, for example, a variable of type object, you'll find on that variable also this generic extension method. But actually, you only want this extension method
to be available on your entities. All the entities in our project implement the IEntity interface.
This means you can go to the EntityExtensions class, and you can add here on this copy method a generic type constraint that says where T is IEntity. Now let's go back to the Program.cs file. Now, when you try to call the extension method on this object, you see it doesn't appear anymore in IntelliSense, but it still appears on your entities like, for example, on the Manager. Here, you have that extension method available as the Manager is an IEntity instance.
So far to generic methods. You just define generic type parameters after the method name, and then you can use these types for the method parameters and also for the return type of the methods. Next, you will start with delegates. In the next demonstration, you will learn how to add and use a delegate. Here in the Main method of the Program class, we use the SqlRepository of Employee. Now let's say that you want to be able to pass a method to the SqlRepository constructor that is called every time an employee is added. To do this, you need a delegate. So now let's go to the SqlRepository C# file, and let's implement here this feature. First, let's create in this file a public delegate that returns void, and let's call it ItemAdded. Let's add the parameter of type object, and let's call it item.
A delegate is like a method pointer. This delegate can point to a method that returns void and that has an object parameter. Now on the SqlRepository constructor, let's add an ItemAdded parameter, and let's call it ItemAddedCallback. I initialize it with null. So, let's also add a question mark to this parameter to mark it as nullable. Now,
let's press Ctrl+dot to create and assign this parameter to the field _itemAddedCallback. You see the created field here, and in the constructor is the assignment. Now let's scroll down to the Add method. Here in the Add method, you use the _itemAddedCallback. You can check with the question mark if it's not null, and then you can invoke that callback with the item. So let's pass in here the item that was added. This means the delegate here will invoke the method to which it points.
Now let's go back to the Program.cs file, and let's use that functionality. Here, before the SqlRepository, let's create an ItemAdded delegate instance. Let's point it to a method with the name EmployeeAdded. You can press Ctrl+dot, and Visual Studio suggests you to create this EmployeeAdded method.
Now you can pass this itemAdded delegate to the constructor of the SqlRepository class. This means now that the EmployeeAdded method will be called every time when an employee is added to the SqlRepository. Now let's go down, and let's implement this method. Let's say you want to write to the console the employee's first name. So first, let's create here an employee variable, and let's cast the item that is here of type object to an Employee. Then, let's create the Console.WriteLine statement, and let's write here, Employee added, and let's print out the FirstName of the employee. But now, wouldn't it be great if you would get here already the type employee because that type is already defined here on the SqlRepository? You will learn how to do this next with a generic delegate. In the next demonstration, you will learn how to create a generic delegate. Previously, you have created this itemAdded delegate that points here to this EmployeeAdded method. It would be great if you would get here the employee instead of an object. Then, you wouldn't have to cast the object to an employee to be able to use its first name. So let's change here the parameter type from object to Employee.
Let's remove this line, and let's just take here from that Employee the FirstName property. Let's also rename that parameter by pressing Ctrl+R+R, and let's call it employee. But now you see, the delegate here is underlined in red. When you hover over it, you see here the error, No overload for 'EmployeeAdded' matches delegate 'ItemAdded.' The method here doesn't match the delegate signature, because here we have defined now an employee parameter. But when you look at the delegate in the SqlRepository C# file, you can see that the delegate takes here an object. But now, like with generic methods, you can also define generic delegates by using generic type parameters after the delegate name. So here, you can define the type parameter, and then instead of object, you can take an item of type T.
This means in the SqlRepository, you can create here an ItemAdded delegate, and you take such an ItemAdded delegate as a constructor parameter. When you scroll down to the Add method where the delegate is used, you'll see everything is fine, because the Invoke method takes an item of type T, and we have here an item of type
T. So let's pass in that item. Now let's go back to the Program.cs file. As you can see, the ItemAdded delegate is still underlined in red, but now when you hover over it, you'll see a different error. The error says, Using the generic type ItemAdded requires 1 type arguments.
So in this case, the type is Employee. Let's specify this as type argument. When you actually hover over the constructor of the SqlRepository class, you'll see that it takes an ItemAdded delegate. This means now we can take here an employee as parameter and this will just work.
Chapter 16 Variance with Generic Delegates
In this demonstration, I will ensure that you understand variance with generic delegates. Here in the Main method of our console application, we have created an ItemAdded delegate. Instead of using var, let's also write this explicitly to the left side of the equal sign. Now, let's say you create an ItemAdded delegate. The Manager class inherits from the Employee class. It is more specific. Let's call it managerAdded, and let's try to assign that itemAdded delegate to this variable.
As you can see, it is underlined in red. When you hover it, you get here the error, Cannot implicitly convert type itemAdded to itemAdded. By default, the generic type parameters of generic delegates are invariant. This means you would have to use here also Employee in order to assign the delegate to this variable. When you try to use a more specific type like Manager, you get an error. Like
with generic interfaces, you can define type parameters on a generic delegate as covariant or as contravariant. So, let's go to the Solution Explorer, and let's open the SqlRepository.cs file that contains here the ItemAdded delegate. You can specify here this type parameter with the in keyword as contravariant. This means now when you go back to the Program.cs file, it is totally fine to assign this ItemAdded delegate to an ItemAdded variable.
So, when you call now this managerAdded delegate, you have to pass in a Manager. And as a manager is actually an employee, it works to pass the Manager to this EmployeeAdded method that is wrapped by the delegate. Now let's go back to the SqlRepository.cs file. You can also have the generic type parameter as return value. So, if you return here instead of void the type T, it is underlined in red. And when you hover it, you get the error, Invalid variance: The type parameter 'T' must be covariantly valid on 'ItemAdded'.Invoke(T).'T' is contravariant. Yes, we have defined it here with the in keyword as contravariant. You can define a generic type parameter like here as contravariant, only if it is not used as a return type. If it is used as input and as output, you have to define the type parameter as invariant.
Now, there is also the option that T is not used as input, but only as output. Then you can define the type parameter with the out keyword as covariant. This means when you look at the Program.cs file, you can still assign an ItemAdded delegate to an ItemAdded variable, of course, as it is that type. But you can also use now a less specific type, for example, ItemAdded