Uploaded by rejin92

C# players guide

advertisement
Sold to
eastan.t@hotmail.ca
It is illegal to redistribute this digital book.
Please do not share this file via email, websites,
or any other means. Be mindful about where
you store it and who might gain access to it.
The digital format of this book is only
distributed via https://gumroad.com/l/CbfL. If
you have received this book through any other
means, please report it to
rbwhitaker@outlook.com.
The C# Player’s Guide
Third Edition
RB Whitaker
Starbound Software
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as
trademarks. Where those designations appear in this book, and the author and publisher were aware of
those claims, those designations have been printed with initial capital letters or in all capitals.
The author and publisher of this book have made every effort to ensure that the information in this book
was correct at press time. However, the author and publisher do not assume, and hereby disclaim any
liability to any party for any loss, damage, or disruption caused by errors or omissions, whether such
errors or omissions result from negligence, accident, or any other cause.
Copyright © 2012-2017 by RB Whitaker
All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means,
electronic or mechanical, including photocopying, recording, or by any information storage and retrieval
system, without written permission from the author, except for the inclusion of brief quotations in a
review. For information regarding permissions, write to:
RB Whitaker
rbwhitaker@outlook.com
ISBN-10: 0-9855801-3-5
ISBN-13: 978-0-9855801-3-1
1 Contents at a
Glance
Acknowledgements
xvii
Introduction
xix
Part 1: Getting Started
1.
The C# Programming Language
3
2.
Installing Visual Studio
3.
Hello World: Your First C# Program
10
4.
Comments
19
6
Part 2: The Basics
5.
Variables
25
6.
The C# Type System
31
7.
Basic Math
42
8.
User Input
48
9.
More Math
53
10. Decision Making
60
11. Switch Statements
68
12. Looping
71
13. Arrays
77
14. Enumerations
83
15. Methods
86
16. Value and Reference Types
97
iv
Contents at a Glance
Part 3: Object-Oriented Programming
17. Object-Oriented Basics
107
18. Making Your Own Classes
112
19. Properties
124
20. Tic-Tac-Toe
130
21. Structs
138
22. Inheritance
144
23. Polymorphism, Virtual Methods, and Abstract Classes
151
24. Interfaces
156
25. Using Generics
160
26. Making Generic Types
167
Part 4: Advanced Topics
27. Namespaces and Using Directives
175
28. Methods Revisited
180
29. Reading and Writing Files
190
30. Error Handling and Exceptions
194
31. Pattern Matching
201
32. Delegates
206
33. Events
212
34. Operator Overloading
219
35. Indexers
223
36. Extension Methods
226
37. Lambda Expressions
230
38. Query Expressions
236
39. Threads
245
40. Asynchronous Programming
251
41. Dynamic Objects
259
42. Unsafe Code
265
43. Other Features in C#
271
Part 5: Mastering the Tools
44. The .NET Platform
301
45. Getting the Most from Visual Studio
313
46. Dependencies and Multiple Projects
319
47. Handling Common Compiler Errors
326
48. Debugging Your Code
333
49. How Your Project Files are Organized
339
Contents at a Glance
v
Part 6: Wrapping Up
50. Try It Out!
345
51. What’s Next?
351
Glossary
354
Tables and Charts
369
Index
374
2 Table of Contents
Acknowledgements
xvii
Introduction
xix
The Player’s Guide
xix
How This Book is Organized
xx
Getting the Most from This Book
xxii
I Genuinely Want Your Feedback
xxiii
This Book Comes with Online Content
xxiii
Part 1: Getting Started
1.
2.
3.
The C# Programming Language
3
What is C#?
3
What is the .NET Platform?
4
C# and .NET Versions
5
Installing Visual Studio
6
Versions of Visual Studio
7
The Installation Process
7
C# Programming on Mac and Linux
9
Hello World: Your First C# Program
10
Creating a New Project
10
A Brief Tour of Visual Studio
11
Building Blocks: Projects, Solutions, and Assemblies
12
Modifying Your Project
13
Compiling and Running Your Project
14
A Closer Look at Your Program
16
vii
4.
Whitespace Doesn’t Matter
17
Semicolons
18
Comments
19
What is a Comment?
19
Why Should I Use Comments?
19
How to Make Comments in C#
20
How to Make Good Comments
21
Part 2: The Basics
5.
6.
7.
8.
Variables
25
What is a Variable?
25
Creating Variables
26
Assigning Values to Variables
27
Retrieving the Contents of a Variable
27
How Data is Stored
28
Multiple Declarations and Assignments
29
Good Variable Names
29
The C# Type System
31
An Introduction to the Type System
31
The ‘int’ Type
31
The ‘byte’, ‘short’, and ‘long’ Types
32
The ‘sbyte’, ‘ushort’, ‘uint’, and ‘ulong’ Types
32
The ‘char’ Type
33
The ‘float’, ‘double’, and ‘decimal’ Types
34
The ‘bool’ Type
36
The ‘string’ Type
36
Numeric Literal Variations
38
Type Inference
40
Basic Math
42
Operations and Operators
42
Addition, Subtraction, Multiplication, and Division
43
The Remainder Operator
44
Unary ‘+’ and ‘-‘ Operators
45
Operator Precedence and Parentheses
46
Why the ‘=‘ Sign Doesn’t Mean Equals
46
Compound Assignment Operators
47
User Input
48
User Input from the Console
48
Converting Types
48
A Complete Sample Program
49
viii
9.
Table of Contents
Escape Characters
51
String Interpolation
52
More Math
53
Integer Division
54
Working with Different Types and Casting
55
Division by Zero
56
Infinity, NaN, e, π, MinValue, and MaxValue
56
Overflow and Underflow
57
Incrementing and Decrementing
58
10. Decision Making
60
The ‘if’ Statement
61
The ‘else’ Statement
62
‘else if’ Statements
62
Curly Braces Not Always Needed
63
Relational Operators: ==, !=, <, >, <=, >=
63
Using ‘bool’ in Decision Making
65
The ‘!’ Operator
65
Conditional Operators: && and || (And and Or)
66
Nesting If Statements
66
The Conditional Operator ?:
67
11. Switch Statements
68
The Basics of Switch Statements
68
Types Allowed with Switch Statements
70
No Implicit Fall-Through
70
12. Looping
71
The While Loop
71
The Do-While Loop
73
The For Loop
73
Breaking Out of Loops
74
Continuing to the Next Iteration of the Loop
74
Nesting Loops
75
Still to Come: Foreach
76
13. Arrays
77
What is an Array?
77
Creating Arrays
78
Getting and Setting Values in Arrays
78
More Ways to Create Arrays
79
Array Length
79
Some Examples with Arrays
79
Arrays of Arrays and Multi-Dimensional Arrays
80
The ‘foreach’ Loop
81
ix
14. Enumerations
83
The Basics of Enumerations
83
Why Enumerations are Useful
85
Underlying Types
85
Assigning Numbers to Enumeration Values
85
15. Methods
86
Creating a Method
87
Calling a Method
88
Returning Stuff from a Method
89
Passing Stuff to a Method
91
Passing in Multiple Parameters
91
Method Overloading
92
Revisiting the Convert and Console Classes
94
XML Documentation Comments
94
The Minimum You Need to Know About Recursion
95
16. Value and Reference Types
97
The Stack and the Heap
97
Memory Management and Garbage Collection
98
References
99
Value Types and Reference Types
99
Null: References to Nothing
101
Value and Reference Semantics
102
Part 3: Object-Oriented Programming
17. Object-Oriented Basics
107
Object Classes and Object Instances
107
Working with an Existing Class
108
Using an Instance
109
The Power of Objects
110
Classes are Reference Types
110
18. Making Your Own Classes
112
Creating a New Class
112
Instance Variables
114
Access Modifiers: private and public
114
Constructors
115
Methods
118
The ‘static’ Keyword
120
Using Our Class
121
The ‘internal’ Access Modifier
121
Class Design and Software Engineering
122
x
Table of Contents
19. Properties
124
The Motivation for Properties
124
Creating Properties
125
Different Accessibility Levels
127
Auto-Implemented Properties
127
Object Initializer Syntax
128
Anonymous Types
129
20. Tic-Tac-Toe
130
Requirements
130
High-Level Design
131
Refactoring and Iterative Design
132
The Full Solution
132
21. Structs
138
Creating a Struct
138
Structs vs. Classes
139
Deciding Between a Struct and a Class
140
Prefer Immutable Value Types
141
The Built-In Types are Aliases
142
22. Inheritance
144
Base Classes
145
Derived Classes
145
Using Derived Classes
146
Constructors and Inheritance
147
The ‘protected’ Access Modifier
148
The Base Class of Everything: object
148
Sealed Classes
148
Partial Classes
149
C# Does Not Support Multiple Inheritance
150
23. Polymorphism, Virtual Methods, and Abstract Classes
151
Polymorphism
151
Revisiting the ‘base’ Keyword
153
Abstract Base Classes
154
The ‘new’ Keyword with Methods
154
24. Interfaces
156
What is an Interface?
156
Creating an Interface
157
Using Interfaces
158
Multiple Interfaces and Inheritance
159
25. Using Generics
160
The Motivation for Generics
160
What are Generics?
162
xi
The List Class
162
The IEnumerable<T> Interface
164
The Dictionary Class
165
26. Making Generic Types
167
Creating Your Own Generic Types
167
Using Your Generic Type in Your Class
168
Generic Type Constraints
169
Generic Methods
171
The Default Operator
171
Part 4: Advanced Topics
27. Namespaces and Using Directives
175
Namespaces
175
Fully Qualified Names
176
Using Directives
176
The Error ‘The type or namespace X could not be found’
176
Name Collisions
178
Static Using Directives
179
28. Methods Revisited
180
Local Functions
180
Optional Parameters
181
Named Parameters
182
Variable Number of Parameters
182
The ‘out’ and ‘ref’ Keywords
183
Returning Multiple Values
186
29. Reading and Writing Files
190
The File Class
190
Text-Based Files
192
Binary Files
193
30. Error Handling and Exceptions
194
How Exception Handling Works
195
Catching Exceptions
196
Handling Different Exceptions in Different Ways
197
Throwing Exceptions
197
The ‘finally’ Keyword
199
Exception Filters
200
Some Rules about Throwing Exceptions
200
31. Pattern Matching
201
Contrasted with Regular Expressions
201
The Pattern Concept
202
xii
Table of Contents
Available Patterns
202
Using Patterns in C#
203
Expect Patterns to Expand
205
32. Delegates
206
Delegates: Treating Methods like Objects
206
Creating a Delegate
206
Using Delegates
207
The Delegate and MulticastDelegate Classes
208
Delegate Chaining
209
The Action and Func Delegates
211
33. Events
212
Defining an Event
213
Raising an Event
214
Attaching and Detaching Event Handlers
215
Common Delegate Types Used with Events
216
The Relationship between Delegates and Events
218
34. Operator Overloading
Overloading Operators
35. Indexers
219
220
223
How to Make an Indexer
223
Using Other Types as an Index
224
Index Initializer Syntax
225
36. Extension Methods
Creating an Extension Method
37. Lambda Expressions
226
227
230
The Motivation for Lambda Expressions
230
Lambda Expressions
232
Multiple and Zero Parameters
233
Type Inference Failures and Explicit Types
233
Statement Lambdas
233
Scope in Lambda Expressions
233
Expression-Bodied Members
234
Lambdas vs. Local Functions
235
38. Query Expressions
236
From Clauses
238
Select Clauses
239
Where Clauses
239
Multiple From Clauses
239
Let Clauses
240
Join Clauses
240
Orderby Clauses
240
xiii
Group Clauses
241
Into Clauses
242
Group Joins
242
Query Syntax and Method Call Syntax
243
Queries are Lazy When Possible
243
39. Threads
245
Threading Code Basics
246
Using ParameterizedThreadStart
247
Thread Safety
249
40. Asynchronous Programming
251
What is Asynchronous Programming?
251
Approaches from the Early Days
252
The Task-based Asynchronous Pattern
255
The ‘async’ and ‘await’ Keywords
256
41. Dynamic Objects
259
Dynamic Type Checking
260
Dynamic Objects and the Dynamic Language Runtime
260
Emulating Dynamic Objects with Dictionaries
261
ExpandoObject
262
Extending DynamicObject
262
When to Use Dynamic Object Variations
264
42. Unsafe Code
265
Unsafe Contexts
265
Pointer Types
266
Stack Allocations
267
Fixed Statements
268
Fixed Size Arrays
269
Calling Native Code with Platform Invocation Services
270
43. Other Features in C#
271
Iterators and the Yield Keyword
272
Constants
273
Attributes
274
The ‘nameof’ Operator
275
The ‘sizeof’ Operator
276
Bit Fields
277
Reflection
280
Using Statements and the IDisposable Interface
280
Preprocessor Directives
281
Nullable Types
283
Simple Null Checks: Null Propagation Operators
283
xiv
Table of Contents
Command Line Arguments
285
User-Defined Conversions
286
The Notorious ‘goto’ Keyword
287
Generic Covariance and Contravariance
290
Advanced Namespace Management
293
Checked and Unchecked Contexts
294
Volatile Fields
295
Part 5: Mastering the Tools
44. The .NET Platform
301
Overview of the .NET Platform
301
A Brief History of the .NET Platform
304
Binary, Assembly, and Compilers
304
Virtual Machines and the Common Language Runtime
306
The .NET Standard Library
308
The .NET Framework
309
.NET Core
310
Xamarin
310
App Models
311
45. Getting the Most from Visual Studio
313
Windows
313
The Options Dialog
315
Including and Excluding Files
315
Showing Line Numbers
316
IntelliSense
316
Basic Refactoring
317
Keyboard Shortcuts
317
46. Dependencies and Multiple Projects
319
Adding DLL References
320
NuGet Packages
321
Creating and Referencing Multiple Projects
323
47. Handling Common Compiler Errors
326
Understanding Compiler Errors
326
Compiler Warnings
326
Common Compiler Errors
327
General Tips for Handling Errors
331
48. Debugging Your Code
333
Launching Your Program in Debug Mode
333
Viewing Exceptions
334
Editing Your Code While Debugging
335
Breakpoints
336
xv
Stepping Through Your Program
49. How Your Project Files are Organized
336
339
Visual Studio’s Projects Directory
340
The Solution Directory
340
The Project Directory
341
Part 6: Wrapping Up
50. Try It Out!
345
Message from Julius Caesar
346
Reverse It!
346
Pig Dice
347
Connect Four
347
Conway’s Game of Life
348
51. What’s Next?
351
Other Frameworks and Libraries
351
Other Topics
352
Make Some Programs
352
Where Do I Go to Get Help?
353
Parting Words
353
Glossary
354
Tables and Charts
369
Index
374
3 Acknowledgements
The task of writing a book is like writing software. When you start, you’re sure it’s only going to take a few
weeks. It’ll be easy, you think. But as you start working, you start seeing that you’re going to need to make
changes, and lots of them. You need to rearrange entire chapters, add topics you hadn’t even thought
about, and you discover that there’s not even going to be a place in your book for that chapter called
Muppets of the Eastern Seaboard.
I couldn’t have ever finished this book without help. I’ll start by thanking Jack Wall, Sam Hulick, Clint
Mansell, and the others who wrote the music for Mass Effect. (You think I’m joking, don’t you?) I listened to
their music nearly incessantly as I wrote this book. Because of them, every moment of the creation of this
book felt absolutely epic.
I need to also thank the many visitors to my game development tutorials site, who provided feedback on
the early versions of this work. In particular, I want to thank Jonathan Loh, Thomas Allen, Daniel Bickler,
and Mete ÇOM, who went way above and beyond, spending hours of their own personal time, reading
through this book and provided detailed critique and corrections. With their help, this book is far more
useful and valuable.
I also need to thank my mom and dad. Their confidence in me and their encouragement to always do the
best I can has caused me to do things I never could have done without them.
Most of all, I want to thank my beautiful wife, who was there to lift my spirits when the weight of writing a
book became unbearable, who read through my book and gave honest, thoughtful, and creative feedback
and guidance, and who lovingly pressed me to keep going on this book, day after day. Without her, this
book would still be a random scattering of Word documents, buried in some obscure folder on my
computer, collecting green silicon-based mold.
To all of you, I owe you my sincerest gratitude.
-RB Whitaker
4 Introduction
In a Nutshell



Describes the goals of this book, which is to function like a player’s guide, not a
comprehensive cover-everything-that-ever-existed book.
Breaks down how the book is organized from a high-level perspective, as well as pointing out
some of the extra “features” of the book.
Provides some ideas on how to get the most out of this book for programmers, beginners,
and anyone who is short on time.
The Player’s Guide
This book is not about playing video games. (Though programming is as fun as playing video games for
many people.) Nor is it about making video games, specifically. (Though you definitely can make video
games with C#.)
Instead, think of this book like a player’s guide, but for a programming language. A player’s guide is a
popular kind of book that is written to help game players:






learn the basics of the game,
prevent them from getting stuck,
understand how the world they’re playing in works,
learn how to overcome the obstacles and enemies they face,
point out common pitfalls they may face and locate useful items,
and master the tools they’re given.
This book accomplishes those same goals for the C# programming language. I’ll walk you through the
language from the ground up, point out places where people get stuck, provide you with hands-on
examples to explore, give you quizzes to ensure you’re on the right track, and describe how to use the
tools that you’ll need to create programs. I’ll show you the ins and outs of the many features of C#,
describing why things work the way they do, rather than just simple mechanics and syntax.
xx
Introduction
My goal is to provide you with the “dungeon map” to direct you as you begin delving into C#, while still
allowing you to mostly explore whatever you want, whenever you want.
I want to point out that this book is intentionally not called Everything you Need to Know about C#, or The
Comprehensive Guide to C#. (Note that if books with those titles actually exist, I’m not referring to them
specifically, but rather, to just the general idea of an all-encompassing book.) I’m here to tell you, when
you’re done with this book, you’ll still have lots to learn about C#.
But guess what? That’s going to happen with any book you use, including those all-encompassing books.
Programming languages are complex creations, and there are enough dark corners and strange
combinations that nobody can learn everything there is to know about them. In fact, I’ve even seen the
people who designed the C# language say they just learned something new about it! For as long as you
use C#, you’ll constantly be learning new things about it, and that’s actually one of the things that makes
programming interesting.
I’ve tried to cover a lot of ground in this book, and with roughly 400 pages, anyone would expect that to
be quite a bit. And it is. But there are plenty of other books out there that are 800 or even 1200 pages
long. A book so heavy, you’ll need a packing mule to carry it anywhere. That, or permanently place it on
the central dais of an ancient library, with a single beam of dusty light shining in on it through a hole in
the marble ceiling. Instead of all that, the goal of this book is effectiveness and clarity, not
comprehensiveness. Something that will fit both on your shelf and in your brain.
It is important to point out that this book is focused on the C# programming language, rather than
libraries for building certain specific application types. So while you can build desktop applications, web
pages, and computer games with C#, we won’t be discussing WPF, ASP.NET, DirectX, or any other
platform- or framework-specific code. Instead, we’ll focus on core C# code, without bogging you down
with those additional libraries at first. Once you’ve got the hang of C#, heading into one of those areas will
be much easier.
How This Book is Organized
This book is divided into six parts. Part 1 describes what you need to get going. You’ll learn how to get set
up with the free software that you need to write code and make your first C# program.
Part 2 describes the basics of procedural programming—how to tell the computer, step-by-step, what to
do to accomplish tasks. It covers things like how information is stored (in variables), how to make
decisions, loop over things repeatedly, and put blocks of code that accomplish specific tasks into a
reusable chunk called a method. It also introduces the type system of the C# language, which is one of
the key pieces of C# programming.
Part 3 goes into object-oriented programming, introducing it from the ground up, but also getting into a
lot of the details that make it so powerful. Chapter 20, in my opinion, is the critical point of the book. By
Chapter 19, we’ve introduced all of the key concepts needed to make almost any C# program, including
classes, which is the most powerful way C# provides for building your own data types. Chapter 20
contains the task (and solution) to making a simple but complete game of Tic-Tac-Toe, which will put all of
the knowledge from the earlier chapters to the test. Everything we do after this chapter is simply fleshing
out details and giving you better tools to get specific jobs done faster.
Part 4 covers some common programming tasks, as well as covering some of the more advanced features
of C#. For the most part, these topics are independent of each other, and once you’ve made it past that
critical point in Chapter 20, you should be able to do these at any time you want.
How This Book is Organized
xxi
Part 5 changes gears, and covers more details about Visual Studio, which you use to create C# programs,
additional information about the .NET Platform, and some tools, tricks, and information you can use as
you program.
Finally, Part 6 wraps up the book with some larger scale programs for you to try making, a chapter on
where to go next as you continue to learn C#, and a glossary of words that are defined throughout the
book, which you can use as a reference when you run across a word or phrase that you are unfamiliar
with or have forgotten about.
Try It Out!
Scattered throughout the book are a variety of sections labeled Try It Out! These sections give you simple
challenge problems and quizzes that give you a chance to play around with the new concepts in the
chapter and test your understanding. If this were a class, these would be the homework.
The purpose of these Try It Out! sections is to help you get some real world practice with the new
information. You can’t learn to drive a car by reading the owner’s manual, and you can’t learn to program
without writing any code.
I strongly encourage you to spend at least a few minutes doing each of these challenges to help you
understand what you’re reading and ensure that you’ve learned what you needed to.
If you have something else you want to explore with the new concepts instead of the challenges I’ve
provided, all the better. The only thing better than playing around with this stuff is doing something with
it that you have a personal interest in. If you want to explore a different direction, go for it!
At the end of the book, in Chapter 50, I have an entire chapter full of larger, tougher challenge problems
for you to try out. These problems involve combining concepts from many chapters together into one
program. Going through some or all of these as you’re finishing up will be a great way to make sure you’ve
learned the most important things you needed to.
The most important thing to remember about these Try It Out! sections is that the answers are all online. If
you get stuck, or just want to compare your solution to someone else’s, you can see mine at
starboundsoftware.com/books/c-sharp/try-it-out/. I should point out that just because your solution
is different from mine (or anyone else’s) doesn’t necessarily mean it is wrong. That’s one of the best parts
about programming—there’s always more than one way to do something.
In a Nutshell
At the beginning of each chapter, I summarize what it contains. These sections are designed to do the
following:



Summarize the chapter to come.
Show enough of the chapter so that an experienced programmer can know if they already know
enough to skip the chapter or if they need to study it in depth.
Review the chapter enough to ensure that you got what you needed to from the chapter. For
instance, imagine you’re about to take a test on C#. You can jump from chapter to chapter,
reading the In a Nutshell sections, and anything it describes that you didn’t already know, you can
then go into the chapter and review it.
In Depth
On occasion, there are a few topics that are not critical to your understanding of C#, but they are an
interesting topic that is related to the things you’re learning. You’ll find this information pulled out into In
Depth sections. These are never required reading, so if you’re busy, skip ahead. If you’re not too busy, I
think you’ll find this additional information interesting, and worth taking the time to read.
xxii
Introduction
Glossary
As you go through this book, you’re going to learn a ton of new words and phrases. Especially if you’re
completely new to programming in general. At the back of this book is a glossary that contains the
definitions for these words. You can use this as a reference in case you forget what a word means, or as
you run into new concepts as you learn C#.
Getting the Most from This Book
For Programmers
If you are a programmer, particularly one who already knows a programming language that is related to
C# (C, C++, Java, Visual Basic .NET, etc.) learning C# is going to be relatively easy for you.
C# has a lot in common with all of these languages. In fact, it’s fair to say that all programming languages
affect and are inspired by other languages, because they evolve over time. C# looks and feels like a
combination of Java and C++, both of which have roots that go back to the C programming language.
Visual Basic .NET (VB.NET) on the other hand, looks and feels quite different from C# (it’s based on Visual
Basic, and Basic before that) but because both C# and VB.NET are designed and built for the .NET
Platform, they have many of the same features, and there’s almost a one-to-one correspondence between
features and keywords.
Because C# is so closely tied to these other languages, and knowing that many people may already know
something about these other languages, you’ll see me point out how C# compares to these other
languages from time to time.
If you already know a lot about programming, you’re going to be able to move quickly through this book,
especially the beginning, where you may find very few differences from languages you already know. To
speed the process along, read the In a Nutshell section at the start of the chapter. If you feel like you
already know everything it describes, it’s probably safe to skip to the next chapter.
I want to mention a couple of chapters that might be a little dangerous to skip. Chapter 6 introduces the
C# type system, including a few concepts that are key to building types throughout the book. Also,
Chapter 16 is sort of a continuation on the type system, describing value and reference types. It’s
important to understand the topics covered in those chapters. Those chapters cover some of the
fundamental ways that C# is different from these other languages, so don’t skip them.
For Busy People
One of the best parts about this book is that you don’t need to read it all. Yes, that’s right. It’s not all
mandatory reading to get started with C#. You could easily get away with only reading a part of this book,
and still understand C#. In fact, not only understand it, but be able to make just about any program you
can dream up. This is especially true if you already know a similar programming language.
At a minimum, you should start at the beginning and read through Chapter 20. That covers the basics of
programming, all the way up to and including an introduction to making your own classes. (And if you’re
already a programmer, you should be able to fly through those introductory chapters quickly.)
The rest of the book could theoretically be skipped, though if you try to use someone else’s code, you’re
probably going to be in for some surprises.
Once you’ve gone through those 20 chapters, you can then come back and read the rest of the book in
more or less any order that you want, as you have extra time.
For Beginners
If you’ve never done any programming before, be warned: learning a programming language can be hard
work. The concepts in the first 20 chapters of this book are the most important to understand. Take
I Genuinely Want Your Feedback
xxiii
whatever time is necessary to really feel like you understand what you’re seeing in these chapters. This
gets you all of the basics, and gets you up to a point where you can make your own types using classes.
Like with the For Busy People section above, Chapter 20 is the critical point that you’ve got to get to, in
order to really understand C#. At that point, you can probably make any program that you can think of,
though the rest of the book will cover additional tools and tricks that will allow you to do this more easily
and more efficiently.
After reading through these chapters, skim through the rest of the book, so that you’re aware of what else
C# has. That’s an important step if you’re a beginner. It will familiarize you with what C# has to offer, and
when you either see it in someone else’s code or have a need for it, you’ll know exactly where to come
back to. A lot of these additional details will make the most sense when you have an actual need for it in a
program of your own creation. After a few weeks or a few months, when you’ve had a chance to make
some programs on your own, come back and go through the rest of the book in depth.
I Genuinely Want Your Feedback
Writing a book is a huge task, and no one has ever finished a huge task perfectly. There’s the possibility of
mistakes, plenty of chances to inadvertently leave you confused, or leaving out important details. I was
tempted to keep this book safe on my hard drive, and never give it out to the world, because then those
limitations wouldn’t matter. But alas, my wife wouldn’t let me follow Gandalf’s advice and “keep it secret;
keep it safe,” and so now here it is in your hands.
If you ever find any problems with this book, big or small, or if you have any suggestions for improving it,
I’d really like to know. After all, books are a lot like software, and there’s always the opportunity for future
versions that improve upon the current one. Also, if you have positive things to say about the book, I’d
love to hear about that too. There’s nothing quite like hearing that your hard work has helped somebody.
To give feedback of any kind, please visit starboundsoftware.com/books/c-sharp/feedback.
This Book Comes with Online Content
On my website, I have a small amount of additional content that you might find useful. For starters, as
people submit feedback, like I described in the last section, I will post corrections and clarifications as
needed on this book’s errata page: starboundsoftware.com/books/c-sharp/errata.
Also on my site, I will post my own answers for all of the Try It Out! sections found throughout this book. If
you get stuck, or just want something to compare your answers with, you can visit this book’s site and see
a solution. To see these answers, go to: starboundsoftware.com/books/c-sharp/try-it-out/.
The website also contains some extra problems to work on, beyond the ones contained in this book. I’ve
been frequently asked to add more problems to the book than what it currently has. Indeed, this version
contains more than any previous version. But at the same time, most people don’t actually do these
problems. To avoid drowning out the actual content with more and more problems, I’ve provided
additional problems on the website. This felt like a good compromise. These can be found at
starboundsoftware.com/books/c-sharp/additional-problems.
Additional information or resources may be found at starboundsoftware.com/books/c-sharp.
Part
1
Getting Started
The world of C# programming lies in front of you, waiting to be explored. In Part 1 of this book, within
just a few short chapters, we’ll do the following:





Get a quick introduction to what C# is (Chapter 1).
Get set up to start making C# programs (Chapter 2).
Write our first program (Chapter 3).
Dig into the fundamental parts of C# programming (Chapters 3 and 4).
1
1 The C# Programming
Language
In a Nutshell



Describes the general idea of programming, and goes into more details about why C# is a
good language.
Describes the core of what the .NET Platform is.
Gives some history on the C# programming language for context.
I’m going to start off this book with a very brief introduction to C#. If you’re already a programmer, and
you’ve read the Wikipedia pages on C# and the .NET Framework, skip ahead to the next chapter.
On the other hand, if you’re new to programming in general, or you’re still a little vague on what exactly
C# or the .NET Platform is, then this is the place for you.
I should point out that we’ll get into a lot of detail about how the .NET Platform functions, and what it
gives you as a programmer in Chapter 44. This chapter just provides a quick overview of the basics.
What is C#?
Computers only understand binary: 1’s and 0’s. All of the information they keep track of is ultimately
nothing more than a glorified pile of bits. All of the instructions they run and all of the data they process
are binary.
But humans are notoriously bad at doing anything with a giant pile of 1’s and 0’s. So rather than doing
that, we created programming languages, which are based on human languages (usually English) and
structured in a way that allows you to give instructions to the computer. These instructions are called
source code, and are simple text files.
When the time is right, your source code will be handed off to a special program called a compiler, which
is able to take it and turn it into the binary 1’s and 0’s that the computer understands, typically in the form
4
Chapter 1
The C# Programming Language
of an EXE file. In this sense, you can think of the compiler as a translator from your source code to the
binary machine instructions that the computer knows.
There are thousands, maybe tens of thousands of programming languages, each good at certain things,
and less good at other things. C# is one of the most popular. C# is a simple general-purpose
programming language, meaning you can use it to create pretty much anything, including desktop
applications, server-side code for websites, and even video games.
C# provides an excellent balance between ease of use and power. There are other languages that provide
less power and are easier to use (like Java) and others that provide more power, giving up some of its
simplicity (like C++). Because of the balance it strikes, it is the perfect language for nearly everything that
you will want to do, so it’s a great language to learn, whether it’s your first or your tenth.
What is the .NET Platform?
C# relies heavily on something called the .NET Platform. It is also commonly also called the .NET
Framework, though we’ll make a subtle distinction between the two later on. The .NET Platform is a large
and powerful platform, which we’ll discuss in detail in Chapter 44. You can go read it as soon as you’re
done with this chapter, if you want.
The .NET Platform is vast, with many components, but two stand out as the most central. The first part is
the Common Language Runtime, often abbreviated as the CLR. The CLR is a software program that takes
your compiled C# code and runs it. When you launch your EXE file, the CLR will start up and begin taking
your code and translating it into the optimal binary instructions for the physical computer that it is
running on, and your code comes to life.
In this sense, the CLR is a middle-man between your code and the physical computer. This type of
program is called a virtual machine. We’ll get into more of the specifics in Chapter 44. For now, it’s only
important to know that the .NET Platform itself, specifically the CLR runtime, play a key role in running
your application—and in making it so your application can run on a wide variety of computer
architectures and operating systems.
The second major component of the .NET Platform is the .NET Standard Library. The Standard Library is
frequently called the Base Class Library. The Standard Library is a massive collection of code that you can
reuse within your own programs to accelerate the development of whatever you are working on. We will
cover some of the most important things in the Standard Library in this book, but it is huge, and deserves
a book of its own. More detail on the Standard Library and the Base Class Library can be found in Chapter
44.
Built on top of the .NET Standard Library is a collection of app models. An app model is another large
library designed for a specific type of application. This includes things like WPF and Windows Forms for
GUI applications, ASP.NET for web development, and Xamarin for iOS and Android development. Game
frameworks or engines like MonoGame and Unity could also be considered app models, though these are
not maintained directly by Microsoft.
This book, unfortunately, doesn’t cover these app models to any serious extent. There are two reasons for
this. Each app model is gigantic. You could write multiple books about each of these app models (and
indeed, there are many books out there). Trying to pack them all into this book would make it a 5000 page
book.
Second, the app models are, true to their name, specific to a certain type of application. This means that
the things that are important to somebody doing desktop development are going to be wildly different
from somebody doing web development. This book focuses on the C# language itself, and the aspects of
C# and .NET Versions
5
the .NET Platform that are useful to everybody. Once you’ve finished this book, you could then proceed on
to other books that focus on specific app models. (Those books all generally assume you know C#
anyway.)
We will cover how the .NET Platform is organized and how it functions in depth in Chapter 44.
C# and .NET Versions
C# has gone through quite a bit of evolution over its history. The first release was in 2002, and established
the bulk of the language features C# still has today.
A little over a year later, in 2003, C# 2.0 was released, adding in a lot of other big and powerful features,
most of which will get quite a bit of attention in this book (generics, nullable types, delegates, static
classes, etc.)
C# 3.0 expanded the language in a couple of very specific directions: LINQ and lambdas, both of which get
their own chapters in this book.
The next two releases were somewhat smaller. C# 4.0 added dynamic typing, as well as named and
optional method arguments. C# 5.0 added greatly expanded support for asynchronous programming.
In the C# 5 era, a new C# compiler was introduced: Roslyn. This compiler has a number of notable
features: it’s open source, it’s written in C# (written in the language it’s for), and it is available while your
program is running (so you can compile additional code dynamically). Something about its construction
also allows for people to more easily tweak and experiment with new features, which led to the features
added in C# 6.0 and 7.0.
C# 6.0 and 7.0 added a whole slew of little additions and tweaks across the language. While previous
updates to the language could usually be summed up in a single bullet point or two, and are given their
own chapters in this book, the new features in C# 6.0 and 7.0 are small and numerous. I try to point out
what these new features are throughout this book, so that you are aware of them.
Alongside the C# language itself, both Visual Studio and the Standard Library have both been evolving
and growing. This book has been updated to work with Visual Studio 2017 and C# 7.0 at the time of
publishing.
Future versions will, of course, arrive before long. Based on past experience, it’s a safe bet that everything
you learn in this book will still apply in future versions.
2
2 Installing Visual Studio
In a Nutshell




To program in C#, we will need a program that allows us to write C# code and run it. That
program is Microsoft Visual Studio.
A variety of versions of Visual Studio exists, including the free Community Edition, as well as
several higher tiers that offer additional features at a cost.
You do not need to spend money to make C# programs.
This chapter walks you through the various versions of Visual Studio to help you decide which
one to use, but as you are getting started, you should consider the free Visual Studio 2017
Community Edition.
To make your own programs, people usually use a program called an Integrated Development Environment
(IDE). An IDE combines all of the tools you will commonly need to make software, including a special text
editor designed for editing source code files, a compiler, and other various tools to help you manage the
files in your project.
With C#, nearly everyone chooses to use some variation of Visual Studio, made by Microsoft. There are a
few different levels of Visual Studio, ranging from the free Community Edition, to the high-end Enterprise
Edition. In this chapter, I’ll guide you through the process of determining which one to choose.
As of the time of publication of this book, the latest version is the 2017 family. There will inevitably be
future releases, but the bulk of what’s described in this book should still largely apply in future versions.
While new features have been added over time, the fundamentals of Visual Studio have remained the
same for a very long time now.
There are three main flavors of Visual Studio 2017. Our first stop will be to look at the differences among
these, and I’ll point out one that is most likely your best choice, getting started. (It’s free, don’t worry!) I’ll
then tell you how to download Visual Studio and a little about the installation process. By the end of this
chapter, you’ll be up and running, ready to start doing some C# programming!
Versions of Visual Studio
7
Versions of Visual Studio
Visual Studio 2017 comes in three editions: Community, Professional, and Enterprise. While I’m ultimately
going to recommend the Community Edition (it’s free, and it still allows you to make and sell commercial
applications with it) it is worth briefly considering the differences between the three.
From a raw feature standpoint, Community and Professional are essentially the same thing. Enterprise
comes with some nice added bonuses, but at a significantly higher cost. These extra features generally
are non-code-related, but instead deal with the surrounding issues, like team collaboration, testing,
performance analysis, etc. While nice, these extra features are probably not a great use of your money as
you’re learning, unless you work for a company or attend school at a place that will buy it for you.
Now that I’ve pushed you away from Enterprise as a beginner, the only remaining question is what to
actually use. And to answer that, we need to compare the Community Edition to the Professional Edition.
Community and Professional are essentially the same product with a different license. Microsoft wants to
make Visual Studio available to everybody, but they still want to be able to bring in money for their
efforts. With the current licensing model, they’ve managed to do that pretty well.
While Professional costs roughly $500, Community is free. But the license prevents certain people (the
ones with tons of money) from using it. While the following interpretation is not legally binding, the
general interpretation of the Community license is essentially this:
You can use it to make software, both commercially and non-commercially, as long as you don’t fit in one
of the following categories:



You have 5+ Visual Studio developers in your company. (If you’re getting it for personal home use
or moonlighting projects, you don’t count the place you work for. You have 1 developer.)
You have 250+ computers or users in your company.
You have a gross income of $1,000,000.
If any of the above apply, you don’t qualify for the Community license, and you must buy Professional. But
then again, if any of those apply to you, you probably have the money to pay for Professional anyway.
There are a couple of exceptions to that:


You’re a student or educator, using it solely for educational uses.
You’re working solely on open source projects.
In short, for essentially everybody reading this book, you should be able to either use Community, or
you’re working somewhere that can afford to buy Professional or Enterprise for you.
This makes the decision simple: you will almost certainly want Visual Studio Community for now.
The Installation Process
Visual Studio can be downloaded from https://www.visualstudio.com/downloads. This will actually
install the Visual Studio Installer, which is a separate program from Visual Studio itself. (It sounds
complicated, but the Visual Studio Installer is a powerful and useful product in its own right.)
Once you get the installer downloaded and running, you will see a screen that looks similar to this:
8
Chapter 2
Installing Visual Studio
If instead of the above, you see a screen that lists Visual Studio Community, Professional, and Enterprise,
choose to install Visual Studio Community (or the option that is the one you need) and you will arrive at
this screen.
Visual Studio, with every single bit of functionality, is a lumbering behemoth of a product. Starting with
Visual Studio 2017, the product and the installer got a massive rewrite to allow you to install only the
components you actually care about. The screen that you see in the previous image is the part of the
installer that allows you to choose what components you want.
By default, nothing is checked, which would give you a very barebones Visual Studio. That’s probably not
what you want. Instead, we need to check the items that we want to include.
For this book, you will want to check the box for .NET desktop development on the Workloads tab.
Feel free to look through the rest of the things and check anything else you might want to play around
with at some point.
The installer contains three tabs at the top. The Individual components tab lets you pick and choose
individual items that you might want a la carte. The Workloads tab will pre-select groups of items on the
Individual components tab, to give you groups of items that are well suited for making specific types of
applications. The Language packs tab is for choosing languages for Visual Studio. English is included by
default, but check the box on other languages if you want to be able to use a different language like
French or Russian.
When you have the components you want (at a minimum, .NET desktop development) hit the Install
button and your selected components will be installed for you.
You may get an icon for Visual Studio on your desktop, but you’ll also always be able to find Visual Studio
in your Start Menu under “Visual Studio 2017.”
Also, if you ever want to modify the components that you’ve installed (either to remove unused ones or
add new ones) you can find “Visual Studio Installer” on your start menu as well, and simply re-run it to
modify your Visual Studio settings and add or remove components.
Visual Studio will ask you to sign in with a Microsoft account at some point. If you don’t have one, you can
follow their instructions to make one.
Starting in the next chapter and throughout the rest of this book, we’ll cover all sorts of useful tips and
tricks on using Visual Studio. Towards the end of this book, in Part 5, we’ll get into Visual Studio in a little
C# Programming on Mac and Linux
9
more depth. Once you get through the next couple of chapters, you can jump ahead to that part
whenever you’re ready for it. You don’t have to read through the book in order.
This book will use screenshots from Visual Studio 2017 Community. You may see some slight differences
depending on which version of Visual Studio you’re using and what add-ons you have active. But all of the
things we talk about in this book will be available in all editions.
Try It Out!
Install Visual Studio. Take the time now to choose a version of Visual Studio and install it, so that
you’re ready to begin making awesome programs in the next chapter.
C# Programming on Mac and Linux
If you are interested in doing C# programming on a Mac or on Linux, you’re still in luck.
Your first option is Visual Studio Code, which can be grabbed from https://code.visualstudio.com. Visual
Studio Code is a lightweight version of Visual Studio that runs on Windows, Mac, and Linux. Visual Studio
Code is missing a number of significant features that this book talks about, but it does support the basics
of editing and compiling your code.
Your second option is Xamarin Studio (http://xamarin.com/studio), which works on macOS. Xamarin
Studio is a powerful, full IDE similar to Visual Studio. In fact, Microsoft is releasing Visual Studio for Mac,
but it is essentially just Xamarin Studio rebranded. (Xamarin is owned by Microsoft, so they’re on the same
team.)
3
3 Hello World: Your First C#
Program
In a Nutshell




Start a new C# Console Application by going to File > New > Project..., choosing the Console
Application template, and giving your project a name.
Inside of the Main method, you can add code to write out stuff using a statement like
Console.WriteLine("Hello World!");
Compile and run your program with F5 or Ctrl + F5.
The template includes code that does the following:
 using directives make it easy to access chunks of previously written code in the
current program.
 The namespace block puts all of the contained code into a single collection.
 The code we actually write goes into the Program class in a method called Main,
which the C# compiler recognizes as the starting point for a program.
In this chapter we’ll make our very first C# program. Our first program needs to be one that simply prints
out some variation of “Hello World!” or we’ll make the programming gods mad. It’s tradition to make your
first program print out a simple message like this whenever you learn a new language. It’s simple, yet still
gives us enough to see the basics of how the programming language works. Also, it gives us a chance to
compile and run a program, with very little chance for introducing bugs.
So that’s where we’ll start. We’ll create a new project and add in a single line to display "Hello World!" Once
we’ve got that, we’ll compile and run it, and you’ll have your very first program!
After that, we’ll take a minute and look at the code that you have written in more detail before moving on
to more difficult, but infinitely more awesome stuff in the future!
Creating a New Project
Let’s get started with our first C# program! Open up Visual Studio, which we installed in Chapter 2.
A Brief Tour of Visual Studio
11
When the program first opens, you will see the Start Page come up. To create a new project, select File >
New > Project... from the menu bar. (Note you can also search for the Console Application template on
the Start Page directly.)
Once you have done this, a dialog will appear asking you to specify a project type and a name for the
project. This dialog is shown below:
On the left side, you will see a few categories of templates to choose from. Depending on what version of
Visual Studio you have installed and what plugins and extensions you have, you may see different
categories here, but the one you’ll want to select is the Visual C# category, which will list all C#-related
templates that are installed.
Once that is selected, in the list in the top-center, find and select the Console Application (.NET
Framework) template. The Console Application template is the simplest template and it is exactly where
we want to start. For all of the stuff we will be doing in this book, this is the template to use.
As you finish up this book, if you want to start doing things like making programs with a graphical user
interface (GUI), game development, smart phone app development, or web-based development, you will
be able to put these other templates to good use.
At the bottom of the dialog, type in a name for your project. I’ve called mine “HelloWorld.” Your project
will be saved in a directory with this name. It doesn’t matter what you call a project, but a good name will
help you find it later. By default, Visual Studio tries to call your programs “ConsoleApplication1” or
“ConsoleApplication2.” If you don’t choose a good name, you won’t know what each of these do. By
default, projects are saved under your Documents directory (Documents/Visual Studio 2017/Projects/).
Finally, press the OK button to create your project!
A Brief Tour of Visual Studio
Once your project has loaded, it is worth a brief discussion of what you see before you. We’ll look in depth
at how Visual Studio works later on (Chapter 45) but it is worth a brief discussion right now.
12
Chapter 3
Hello World: Your First C# Program
By this point, you should be looking at a screen that looks something like this:
Depending on which version of Visual Studio you installed, you may see some slight differences, but it
should look pretty similar to this.
In the center should be some text that starts out with using System;. This is your program’s source code!
It is what you’ll be working on. We’ll discuss what it means, and how to modify it in a second. We’ll spend
most of our time in this window.
On the right side is the Solution Explorer. This shows you a big outline of all of the files contained in your
project, including the main one that we’ll be working with, called “Program.cs”. The *.cs file extension
means it is a text file that contains C# code. If you double-click on any item in the Solution Explorer, it will
open in the main editor window. The Solution Explorer is quite important, and we’ll use it frequently.
As you work on your project, other windows may pop up as they are needed. Each of these can be closed
by clicking on the ‘X’ in the upper right corner of the window.
If, by chance, you are missing a window that you feel you want, you can always open it by finding it on
either the View menu or View > Other Windows. For right now, if you have the main editor window open
with your Program.cs file in it, and the Solution Explorer, you should be good to go.
Building Blocks: Projects, Solutions, and Assemblies
As we get started, it is worth defining a few important terms that you’ll be seeing throughout this book. In
the world of C#, you’ll commonly see the words solution, project, and assembly, and it is worth taking the
time upfront to explain what they are, so that you aren’t lost.
These three words describe the code that you’re building in different ways. We’ll start with a project. A
project is simply a collection of source code and resource files that will all eventually get built into the
same executable program. A project also has additional information telling the compiler how to build it.
When compiled, a project becomes an assembly. In nearly all cases, a single project will become a single
assembly. An assembly shows up in the form of an EXE file or a DLL file. These two different extensions
represent two different types of assemblies, and are built from two different types of projects (chosen in
the project’s settings).
Modifying Your Project
13
A process assembly appears as an EXE file. It is a complete program, and has a starting point defined,
which the computer knows to run when you start up the EXE file. A library assembly appears as a DLL file.
A DLL file does not have a specific starting point defined. Instead, it contains code that other programs
can access and reuse on the fly.
Throughout this book, we’ll be primarily creating and working with projects that are set up to be process
assemblies that compile to EXE files, but you can configure any project to be built as a library assembly
(DLL) instead.
Finally, a solution will combine multiple projects together to accomplish a complete task or form a
complete program. Solutions will also contain information about how the different projects should be
connected to each other. While solutions can contain many projects, most simple programs (including
nearly everything we do in this book) will only need one. Even many large programs can get away with
only a single project.
Looking back at what we learned in the last section about the Solution Explorer, you’ll see that the
Solution Explorer is showing our entire solution as the very top item, which it labels “Solution ‘HelloWorld’
(1 project).” Immediately underneath that, we see the one project that our solution contains: “HelloWorld.”
Inside of the project are all of the settings and files that our project has, including the Program.cs file that
contains source code that we’ll soon start editing.
It’s important to keep the solution and project separated in your head. They both have the same name
and it can be a little confusing. Just remember the top node is the solution, and the one inside it is the
project.
Modifying Your Project
We’re now ready to make our program actually do something. In the center of your Visual Studio window,
you should see the main text editor, containing text that should look identical to this:
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
}
}
}
In a minute we’ll discuss what all of that does, but for now let’s go ahead and make our first change—
adding something that will print out the message “Hello World!”
Right in the middle of that code, you’ll see three lines that say static void Main(string[] args) then a
starting curly brace (‘{‘) and a closing curly brace (‘}’). We want to add our new code right between the two
curly braces.
Here’s the line we want to add:
Console.WriteLine("Hello World!");
So now our program’s full code should look like this:
14
using
using
using
using
using
Chapter 3
Hello World: Your First C# Program
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
We’ve completed our first C# program! Easy, huh?
Try It Out!
Hello World! It’s impossible to understate how important it is to actually do the stuff outlined in this
chapter. Simply reading text just doesn’t cut it. In future chapters, most of these Try It Out! sections
will contain extra things to do, beyond the things described in the actual body of the chapter. But for
right now, it is very important that you simply go through the process explained in this chapter. The
chapter itself is a Try It Out! So follow through this chapter, one step at a time, and make sure you’re
understanding the concepts that come up, at least at a basic level.
Compiling and Running Your Project
Your computer doesn’t magically understand what you’ve written. Instead, it understands special
instructions that are composed of 1’s and 0’s called binary. Fortunately for us, Visual Studio includes a
thing called a compiler. A compiler will take the C# code that we’ve written and turn it into binary that the
computer understands.
So our next step is to compile our code and run it. Visual Studio will make this easy for us.
To start this process, press F5 or choose Debug > Start Debugging from the menu.
There! Did you see it? Your program flashed on the screen for a split second! (Hang on... we’ll fix that in a
second. Stick with me for a moment.)
We just ran our program in debug mode, which means that if something bad happens while your
program is running, it won’t simply crash. Instead, Visual Studio will notice the problem, stop in the middle
of what’s going on, and show you the problem that you are having, allowing you to debug it. We’ll talk
more about how to actually debug your code in Chapter 48.
So there you have it! You’ve made a program, compiled it, and executed it!
If it doesn’t compile and execute, double check to make sure your code looks like the code above.
Help! My program is running, but disappearing before I can see it!
You likely just ran into this problem when you executed your program. You push F5 and the program
runs, a little black console window pops up for a split second before disappearing again, and you have no
clue what happened.
There’s a good reason for that. Your program ran out of things to do, so it finished and closed on its own.
(It thinks it’s so smart, closing on its own like that.)
Compiling and Running Your Project
15
But we’re really going to want a way to make it so that doesn’t happen. After all, we’re left wondering if it
even did what we told it to. There are two solutions to this, each of which has its own strengths and
weaknesses.
Approach #1: When you run it without debugging, console programs like this will always pause before
closing. So one option is to run it without debugging. This option is called Release Mode. We’ll cover this in
a little more depth later on, but the bottom line is that your program runs in a streamlined mode which is
faster, but if something bad happens, your program will just die, without giving you a chance to debug it.
You can run in release mode by simply pressing Ctrl + F5 (instead of just F5). Do this now, and you’ll see
that it prints out your “Hello World!” message, plus another message that says “Press any key to
continue...” which does exactly what it says and waits for you before closing the program. You can also
find this under Debug > Start Without Debugging on the menu.
But there’s a distinct disadvantage to running in release mode. We’re no longer running in debug mode,
and so if something happens with your program while it is running, your application will crash and die.
(Hey, just like all of the other “cool” programs out there!) Which brings us to an alternative approach:
Approach #2: Put another line of code in that makes the program wait before closing the program. You
can do this by simply adding in the following line of code, right below where you put the
Console.WriteLine("Hello World!"); statement:
Console.ReadKey();
So your full code, if you use this approach, would look like this:
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
Console.ReadKey();
}
}
}
Using this approach, there is one more line of code that you have to add to your program (in fact, every
console application you make), which can be a little annoying. But at least with this approach, you can still
run your program in debug mode, which you will soon discover is a really nice feature.
Fortunately, this is only going to be a problem with console apps. That’s what we’ll be doing in this book,
but before long, you’ll probably be making windows apps, games, or awesome C#-based websites, and
this problem will go away on its own. They work in a different way, and this won’t be an issue there.
Try It Out!
See Your Program Twice. I’ve described two approaches for actually seeing your program execute.
Take a moment and try out each approach. This will give you an idea of how these two different
approaches work. Also, try combining the two and see what you get. Can you figure out why you need
to push a key twice to end the program?
16
Chapter 3
Hello World: Your First C# Program
A Closer Look at Your Program
Now that we’ve got our program running, let’s take a minute and look at each line of code in the program
we’ve made. I’ll explain what each one does so that you’ll have a basic understanding of everything in your
simple Hello World program.
Using Directives
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
The first few lines of your program all start with the keyword using. A keyword is simply a reserved word,
or a magic word that is a built-in part of the C# programming language. It has special meaning to the C#
compiler, which it uses to do something special. The using keyword tells the compiler that there is a
whole other pile of code that someone made that we want to be able to access. (This is actually a bit of a
simplification, and we’ll sort out the details in Chapter 27.)
So when you see a statement like using System; you know that there is a whole pile of code out there
named System that our code wants to use. Without this line, the C# compiler won’t know where to find
things and it won’t be able to run your program. You can see that there are five using directives in your
little program that are added by default. We can leave these exactly the way they are for the near future.
Namespaces, Classes, and Methods
Below the using directives, you’ll see a collection of curly braces (‘{‘ and ‘}’) and you’ll see the keywords
namespace, class, and in the middle, the word Main. Namespaces, classes, and methods (which Main is
an example of) are ways of grouping related code together at various levels. Namespaces are the largest
grouping, classes are smaller, and methods are the smallest. We’ll discuss each of these in great depth as
we go through this book, but it is worth a brief introduction now. We’ll start at the smallest and work our
way up.
Methods are a way of consolidating a single task together in a reusable block of code. In other
programming languages, methods are sometimes called functions, procedures, or subroutines. We’ll get
into a lot of detail about how to make and use methods as we go, but the bulk of our discussion about
methods will be in Chapter 15, with some extra details in Chapter 28.
Right in the middle of the generated code, you’ll see the following:
static void Main(string[] args)
{
}
This is a method, which happens to have the name Main. I won’t get into the details about what
everything else on that line does yet, but I want to point out that this particular setup for a method makes
it so that C# knows it can be used as the starting point for your program. Since this is where our program
starts, the computer will run any code we put in here. For the next few chapters, everything we do will be
right in here.
You’ll also notice that there are quite a few curly braces in our code. Curly braces mark the start and end
of code blocks. Every starting curly brace (‘{‘) will have a matching ending curly brace (‘}’) later on. In this
particular part, the curly braces mark the start and end of the Main method. As we discuss classes and
namespaces, you’ll see that they also use curly braces to mark the points where they begin and end. From
looking at the code, you can probably already see that these code blocks can contain other code blocks to
form a hierarchy.
Whitespace Doesn’t Matter
17
When one thing is contained in another, it is said to be a member of it. So the Program class is a member
of the namespace, and the Main method is a member of the Program class.
Classes are a way of grouping together a set of data and methods that operate on that data into a single
reusable package. Classes are the fundamental building block of object-oriented programming. We’ll get
into this in great detail in Part 3, especially Chapters 17 and 18.
In the generated code, you can see the beginning of the class, marked with:
class Program
{
And later on, after the Main method it contains, you’ll see a matching closing curly brace:
}
Program is simply a name for the class. It could have been just about anything else. The fact that the
Main method is contained in the Program class indicates that it belongs to the Program class.
Namespaces are the highest level grouping of code. Many smaller programs may only have a single
namespace, while larger ones often divide the code into several namespaces based on the feature or
component that the code is used in. We’ll spend a little extra time detailing namespaces and using
directives in Chapter 27.
Looking at the generated code, you’ll see that our Program class is contained in a namespace called
“HelloWorld”:
namespace HelloWorld
{
...
}
Once again, the fact that the Program class appears within the HelloWorld namespace means that it
belongs to that namespace, or is a member of it.
Whitespace Doesn’t Matter
In C#, whitespace such as spaces, new lines, and tabs don’t matter to the C# compiler. This means that
technically, you could write any program on a single line! But don’t do that. That would be a bad idea.
Instead, you should use whitespace to help make your code more readable, both for other people who
may look at your code, or even yourself a few weeks from now, when you’ve forgotten what exactly your
code was supposed to do.
I’ll leave the decision about where to put whitespace up to you, but as an example, compare the following
pieces of code that do the same thing:
static void Main(string
[] args) { Console
.WriteLine
"Hello World!"
(
);}
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
For the sake of clarity, I’ll use a style like the bottom version throughout this book.
18
Chapter 3
Hello World: Your First C# Program
Semicolons
You may have noticed that the lines of code we added all ended with semicolons (‘;’).
This is how C# knows it has reached the end of a statement. A statement is a single step or instruction that
does something. We’ll be using semicolons all over the place as we write C# code.
Try It Out!
Evil Computers. In the influential movie 2001: A Space Odyssey, an evil computer named HAL 9000
takes over a Jupiter-bound spaceship, locking Dave, the movie’s hero, out in space. As Dave tries to
get back in, to the ship, he tells HAL to open the pod bay doors. HAL’s response is "I’m sorry, Dave. I’m
afraid I can’t do that." Since we know not all computers are friendly and happy to help people, modify
your Hello World program to say HAL 9000’s famous words, instead of "Hello World!"
This chapter may have seemed long, and we haven’t even accomplished very much. That’s OK, though. We
have to start somewhere, and this is where everyone starts. We have now made our first C# program,
compiled it, and executed it! And just as important, we now have a basic understanding of the starter
code that was generated for us. This really gets us off on the right foot. We’re off to a great start, but
there’s so much more to learn!
4
4 Comments
Quick Start



Comments are a way for you to add text for other people (and yourself) to read. Computers
ignore comments entirely.
Comments are made by putting two slashes (//) in front of the text.
Multi-line comments can also be made by surrounding it with asterisks and slashes, like this:
/* this is a comment */
In this short chapter we’ll cover the basics of comments. We’ll look at what they are, why you should use
them, and how to do them. Many programmers (even many C# books) de-emphasize comments, or
completely ignore them. I’ve decided to put them front and center, right at the beginning of the book—
they really are that important.
What is a Comment?
At its core, a comment is text that is put somewhere for a human to read. Comments are ignored entirely
by the computer.
Why Should I Use Comments?
I mentioned in the last chapter that whitespace should be used to help make your code more readable.
Writing readable and understandable code is a running theme you’ll see in this book. Writing code is
actually far easier than reading it, or trying understanding what it does. And believe it or not, you’ll
actually spend far more time reading code than writing it. You will want to do whatever you can to make
your code easier to read. Comments will go a very long way towards making your code more readable
and understandable.
You should use comments to describe what you are doing so that when you come back to a piece of code
that you wrote after several months (or even just days) you’ll know what you were doing.
Writing comments—wait, let me clarify—writing good comments is a key part of writing good code.
Comments can be used to explain tricky sections of code, or explain what things are supposed to do. They
20
Chapter 4
Comments
are a primary way for a programmer to communicate with another programmer who is looking at their
code. The other programmer may even be on the other side of the world and working for a different
company five years later!
Comments can explain what you are doing, as well as why you are doing it. This helps other
programmers, including yourself, know what was going on in your mind at the time.
In fact, even if you know you’re the only person who will ever see your code, you should still put
comments in it. Do you remember what you ate for lunch a week ago today? Neither do I. Do you really
think that you’ll remember what your code was supposed to do a week after you write it?
Writing comments makes it so that you can quickly understand and remember what the code does, how it
does it, why it does it, and you can even document why you did it one way and not another.
How to Make Comments in C#
There are three basic ways to make comments in C#. For now, we’ll only really consider two of them,
because the third applies only to things that we haven’t looked at yet. We’ll look at the third form of
making comments in Chapter 15.
The first way to create a comment is to start a line with two slashes: //. Anything on the line following the
two slashes will be ignored by the computer. In Visual Studio the comments change color—green, by
default—to indicate that the rest of the line is a comment.
Below is an example of a comment:
// This is a comment, where I can describe what happens next...
Console.WriteLine("Hello World!");
Using this same thing, you can also start a comment at the end of a line of code, which will make it so the
text after the slashes are ignored:
Console.WriteLine("Hello World!"); // This is also a comment.
A second method for creating comments is to use the slash and asterisk combined, surrounding the
comment, like this:
Console.WriteLine("Hi!"); /* This is a comment that ends here... */
This can be used to make multi-line comments like this:
/* This is a multi-line comment.
It spans multiple lines.
Isn't it neat? */
Of course, you can do multi-line comments with the two slashes as well, it just has to be done like this:
//
//
//
This is a multi-line comment.
It spans multiple lines.
Isn't it neat?
In fact, most C# programmers will probably encourage you to use the single line comment version instead
of the /* */ version, though it is up to you.
The third method for creating comments is called XML Documentation Comments, which we’ll discuss
later, because they’re used for things that we haven’t discussed yet. For more information about XML
Documentation Comments, see Chapter 15.
How to Make Good Comments
21
How to Make Good Comments
Commenting your code is easy; making good comments is a little trickier. I want to take some time and
describe some basic principles to help you make comments that will be more effective.
My first rule for making good comments is to write the comments for a particular chunk of code as soon
as you’ve got the piece more or less complete. A few days or a weekend away from the code and you may
no longer really remember what you were doing with it. (Trust me, it happens!)
Second, write comments that add value to the code. Here’s an example of a bad comment:
// Uses Console.WriteLine to print "Hello World!"
Console.WriteLine("Hello World!");
The code itself already says all of that. You might as well not even add it. Here’s a better version:
// Printing "Hello World!" is a very common first program to make.
Console.WriteLine("Hello World!");
This helps to explain why we did this instead of something else.
Third, you don’t need a comment for every single line of code, but it is helpful to have one for every
section of related code. It’s possible to have too many comments, but the dangers of over-commenting
code matter a whole lot less than the dangers of under-commented (or completely uncommented code).
When you write comments, take the time put in anything that you or another programmer may want to
know if they come back and look at the code later. This may include a human-readable description of
what is happening, it may include describing the general method (or algorithm) you’re using to accomplish
a particular task, or it may explain why you’re doing something. You may also find times where it will be
useful to include why you aren’t using a different approach, or to warn another programmer (or yourself!)
that a particular chunk of code is tricky, and you shouldn’t mess with it unless you really know what you’re
doing.
Having said all of this, don’t take it to an extreme. Good comments don’t make up for sloppy, ugly, or hard
to read code. Meanwhile nice, clean, understandable code reduces the times that you need comments at
all. (The code is the authority on what’s happening, not the comments, after all.) Make the code as
readable as possible first, then add just enough comments to fill in the gaps and paint the bigger picture.
When used appropriately, comments can be a programmer’s best friend.
Try It Out!
Comment ALL the things! While it’s overkill, in the name of putting together everything we’ve
learned so far, go back to your Hello World program from the last chapter and add in comments for
each part of the code, describing what each piece is for. This will be a good review of what the pieces
of that simple program do, as well as give you a chance to play around with some comments. Try out
both ways of making comments (// and /* */) to see what you like.
Part
2
The Basics
With a basic understanding of how to get started behind us, we’re ready to dig in and look at the
fundamentals of programming in C#.
It is in this part that our adventure really gets underway. We’ll start learning about the world of C#
programming, and learn about the key tools that we’ll use to get things done.
In this section, we cover aspects of C# programming that are called “procedural programming.” This
means we’ll be learning how to tell the computer, step-by-step, how to get things done.
We’ll look at how to:








Store data in variables (Chapter 5).
Understand the type system (Chapter 6).
Do basic math (Chapters 7 and 9).
Get input from the user (Chapter 8).
Make decisions (Chapter 10).
Repeat things multiple times (Chapter 12 and 13).
Create enumerations (Chapter 14).
Package related code together in a way that allows you to reuse it (Chapter 15).
5
5 Variables
In a Nutshell







You can think of variables as boxes that store information.
A variable has a name, a type, and a value that is contained in it.
You declare (create) a variable by stating the type and name: int number;
You can assign values to a variable with the assignment operator (’=’): number = 14;
When you declare a variable, you can initialize it as well: int number = 14;
You can retrieve the value of a variable simply by using the variable’s name in your code:
Console.WriteLine(number);
This chapter also gives guidelines for good variable names.
In this chapter, we’re going to dig straight into one of the most important parts of programming in C#.
We’re going to discuss variables, which are how we keep track of information in our programs. We’ll look
at how you create them, place different values in them, and use the value that is currently in a variable.
What is a Variable?
A key part of any program you make, regardless of the programming language, is the ability to store
information in memory while the program is running. For example, you might want to store a player’s
score or a person’s name, so that you can refer back to it later or modify it.
You may remember discussing variables in math classes, but these are a little different.
In math, we talk about variables being an “unknown quantity” that you are supposed to
solve for. Variables in math are a specific value that you just need to figure out.
In programming, a variable is a place in memory where you can store information. It’s
like a little box or bucket to put stuff in. At any point in time, you can look up the
contents of the variable or rewrite the contents of the variable with new stuff. When
you do this, the variable itself doesn’t need to change, just the contents in the box.
Each variable has a name and a type. The name is what you’ll use in your program when you want to read
its contents or put new stuff in it.
26
Chapter 5
Variables
The variable’s type indicates what kind of information you can put in it. C# has a large assortment of types
that you can use, including a variety of integer types, floating point (real valued) types, characters, strings
(text), Boolean (true/false), and a whole lot more.
In C#, types are a really big deal. Throughout this book, we’ll spend a lot of time learning how to work with
different types, converting from one type to another, and ultimately building our own types from scratch.
In the next chapter, we’ll get into types in great detail. For now though, let’s look at the basics of creating a
variable.
Creating Variables
Let’s make our first variable. The process of creating a variable is called declaring a variable.
Let’s start by going to Visual Studio and creating a brand new console project, just like we did with the
Hello World project, back in Chapter 3. Inside of the Main method, add the following single line of code:
int score;
So your code should look something like this:
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace Variables
{
class Program
{
static void Main(string[] args)
{
int score;
}
}
}
Congratulations! You’ve made your first variable! When you declare a variable, the computer knows that it
will need to reserve a place in memory for this variable.
As you can see, when you declare a variable, you need to indicate the variable’s name
and type. This one line has both of those parts on it. The first part you see here is int.
This is the variable’s type. We’ll look at the different types that are available in a
minute, but for now all we need to know is that the int type is for storing integers. (In
case you don’t remember from math class, integers are whole numbers and their
negatives, so 0, 1, 2, 3, 4, ..., and -1, -2, -3, -4, ....) Because we’ve made a variable that
stores integers, we know we could put the number 100 in it, or -75946. But we could not store the number
1.3483 (it’s not an integer), and we also could not store a word like “hamburger” (it’s not an integer either).
The variable’s type determines what kind of stuff we can put in it.
The second part of declaring a variable is giving it a name. It is important to remember that a variable’s
name is meant for humans. The computer doesn’t care what it is called. (In fact, once you hand it off to
the computer, it changes the name to a memory location anyway.) So you want to choose a name that
makes sense to humans, and accurately describes what you’re putting in it. In math, we often call
variables by a single letter (like x), but in programming we can be more precise and call it something like
score instead.
Assigning Values to Variables
27
As always, C# statements end with a ‘;’, telling the computer that it has reached the end of the statement.
After this line, we have made a new variable with the name score and a type of int which we can now use!
Assigning Values to Variables
The next thing we want to do is put a value in the variable. This is called assigning a value to the variable,
and it is done using the assignment operator: “=”. The line of code below assigns the value 0 to the score
variable we just created:
score = 0;
You can add this line of code right below the previous line we added.
This use of the equals sign is different than how it is used in math. In math, “=” indicates
that two things are the same, even though they may be written in different formats. In
C# and many other programming languages, it means we’re going to take the stuff on
the right side of the equals sign and place it in the variable that is named on the left.
You can assign any integer value to score, and you can assign different values over time:
score = 4;
score = 11;
score = -1564;
You can assign a value to a variable whenever you want, as long as it is after the variable has been
declared. Of course, we haven’t learned very powerful tools for programming yet, so “whenever you want”
doesn’t mean much yet. (We’ll get there soon, don’t worry!)
When we create a variable, we often want to give it a value right away. (The C# compiler is not a big fan of
you trying to see what’s inside an empty variable box.) While you can declare a variable and assign it a
value in separate steps, it is also possible to do both of them at the same time:
int theMeaningOfLife = 42;
This line creates a variable called theMeaningOfLife with the type int, and gives it a starting value of 42.
Retrieving the Contents of a Variable
As we just saw, we can use the assignment operator (‘=’) to put values into a variable. You can also see and
use the contents of a variable, simply by using the variable’s name. When the computer is running your
code and it encounters a variable name, it will go to the variable, look up the contents inside, and use that
value in its place.
For example, int the code below, the computer will pull the number out of the number variable and write
3 to the console window:
int number = 3;
Console.WriteLine(number); // Console.WriteLine prints lots of things, not just text.
When you access a variable, here’s what the computer does:
1.
2.
3.
Locates the variable that you asked for in memory.
Looks in the contents of the variable to see what value it contains.
Makes a copy of that value to use where it is needed.
The fact that it grabs a copy of the variable is important. For one, it means the variable keeps the value it
had. Reading from a variable doesn’t change the value of the variable. Two, whatever you do with the
28
Chapter 5
Variables
copy won’t affect the original. (We’ll learn more about how this works in Chapter 16, when we learn about
value and reference types.)
For example, here is some code that creates two variables and assigns the value of one to the other:
int a = 5;
int b = 2;
b = a;
a = -3;
With what you’ve learned so far about variables, what value will a and b have after this code runs?
Try It Out!
Playing with Variables. Take the little piece of code above and make a program out of it. Follow the
same steps you did in Chapter 3 when we made the Hello World program, but instead of adding code
to print out “Hello World!”, add the lines above. Use Console.WriteLine, like we did before and print
out the contents of the two variables. Before you run the code, think about what you expect to be
printed out for the a and b variables. Go ahead and run the program. Is it what you expected?
Right at the beginning of those four lines, we create two variables, one named a, and one named b. Both
can store integers, because we’re using the int type. We also assign the value 5 to a, and 2 to b. After the
first two lines, this is what we’re looking at:
We then use the assignment operator to take the value inside of a and copy it to b:
Finally, on the last line we assign a completely new value to a:
If we printed out a and b, we would see that a is -3 and b is 5 by the time this code is finished.
How Data is Stored
Before we move into a discussion about the C# type system, we need to understand a little about how
information is stored on a computer. This is a key part of what drives the need for types in the first place.
Multiple Declarations and Assignments
29
It is important to remember that computers only work with 1’s and 0’s. (Technically, they’re tiny electric
pulses or magnetic fields that can be in one of two states which we label 1 and 0.)
A single 1 or 0 is called a bit, and a grouping of eight of them is called a byte. If we do the math, this means
that a single byte can store up to a total of 256 different states.
To get more states than this, we need to put multiple bytes together. For instance, two bytes can store
65,536 possible states. Four bytes can store over 4 billion states, and eight bytes combined can store over
18 quintillion possible states.
But we need a way to take all of these states and make sense out of them. This is what the type system is
for. It defines how many bytes we need to store different things, and how those bits and bytes will be
interpreted by the computer, and ultimately, the user.
For example, let’s say we want to store letters. Modern systems tend to use two bytes to store letters.
Programmers have assigned each letter a specific pattern of bits. For instance, we assign the capital letter
‘A’ to the bit pattern 00000000 01000001. ‘B’ is one up from that: 00000000 01000010. Because we’re using
two bytes, we have 65,536 different possibilities. That’s enough to store every symbol in every language
that is currently spoken on Earth, including many ancient languages, and still have room to spare.
For each different type of data, we interpret the underlying bits and bytes in a different way. The int type
that we were using earlier works like this. The int type uses four bytes. For brevity, in this discussion, I’m
leaving off the first three bytes, which contain all zeros for the small sample numbers we’re using here.
The value 0 is represented with the bit pattern 00000000. The value 1 is represented with the bit pattern
00000001. 2 is represented with 00000010. 3 is 00000011. This is basically counting in a base two
numbering system, or a binary numbering system.
Other types will use their bits and bytes in other ways. We won’t get into the specifics about how they all
work, as that’s really beyond the scope of this book. I’ll just point out that the way C# interprets bits and
bytes uses the same standard representations as nearly every other language and computer.
Multiple Declarations and Assignments
Our earlier code for creating a variable and for assigning a value to a variable just did one per line. But
you can declare multiple variables at the same time using code like this:
int a, b, c;
If you do this, all variables must be of the same type (int in this case). We’ll talk about types in more depth
in Chapter 6.
You can also assign the same value to multiple different variables all at the same time:
a = b = c = 10;
Most cases will probably lead you to make and assign values individually, rather than simultaneously, but
it is worth knowing that this is an option.
Good Variable Names
Before we go on, let’s talk about how to choose good names for your variables. Not everybody agrees on
what makes a variable name good. But I’m going to give you the rules I follow, which you’ll discover are
pretty typical, and not too far off from what most experienced programmers do.
The purpose of a variable name is to give a human-readable label for the variable. Anyone who stumbles
across the variable name should be able to instantly know what information the variable contains.
30
Chapter 5
Variables
It’s easy to write code. It’s hard to write code that you can actually go back and read and understand. Like
comments, good variable names are an absolutely critical part of writing readable code, and it’s not
something that can be ignored. Here are my rules:
Rule #1: Meet C#’s Requirements. C# has a few requirements for variable names. All variable names
have to start with a letter (a-z or A-Z) or the underscore (‘_’) character, and can then contain any number
of other letters, numbers, or the underscore character. You also cannot name a variable the same thing
as one of the reserved keywords that C# defines. These keywords are highlighted in blue in Visual Studio,
but includes things like namespace, int, and public. Your code won’t compile if you don’t follow this rule.
Rule #2: Variable names should describe the stuff you intend on putting in it. If you are putting a
player’s score in it, call it score, or playerScore, or even plrscr if you have to, but don’t call it jambalaya,
p, or monkey. But speaking of plrscr...
Rule #3: Don’t abbreviate or remove letters. Looking at the example of plrscr, you can tell that it
resembles “player score.” But if you didn’t already know, you’d have to sit there and try to fill in the
missing letters. Is it “plural scar,” or “plastic scrabble”? Nope, it is “player score.” You just have to sit there
and study it. The one exception to this rule is common abbreviations or acronyms. HTML is fine.
Rule #4: A good name will usually be kind of long. In math, we usually use single letters for variable
names. In programming, you usually need more than that to accurately describe what you’re trying to do.
In most cases, you’ll probably have at least three letters. Often, it is 8 to 16. Don’t be afraid if it gets longer
than that. It’s better to be descriptive than to “save letters.”
Rule #5: If your variables end with a number, you probably need a better name. If you’ve got count1
and count2, there’s probably a better name for them. (Or perhaps an array, which we’ll talk about later.)
Rule #6: “data”, “text”, “number”, and “item” are usually not descriptive enough. For some reason,
people seem to fall back to these all the time. They’re OK, but they’re just not very descriptive. It’s best to
come up with something more precise in any situation where you can.
Rule #7: Make the words of the variable name stand out from each other. This is so it is easier to
read a variable name that is composed of multiple words. playerScore (with a capital ‘S’) and
player_score are both more readable than playerscore. My personal preference is the first, but both
work.
Try It Out!
Variables Quiz. Answer the following questions to check your understanding. When you’re done,
check your answers against the ones below. If you missed something, go back and review the section
that talks about it.
1.
2.
3.
4.
Name the three things all variables have.
True/False. You can use a variable before it is declared.
How many times must a variable be declared?
Out of the following, which are legal variable names? answer, 1stValue, value1, $message,
delete-me, delete_me, PI.
Answers: (1) name, type, value. (2) False. (3) 1. (4) answer, value1, delete_me, PI.
6
6 The C# Type System
In a Nutshell






It is important to understand the C# type system.
Integral types (int, short, long, byte, sbyte, uint, ushort, ulong, and char) store integers
with various ranges.
Floating point types (float, double, decimal) store floating point numbers with various levels
of precision and range.
The bool type stores truth values (true/false).
The string type stores text.
Other types will be discussed in future chapters.
An Introduction to the Type System
Now that we understand a bit about how the computer stores information and how raw bits and bytes
are interpreted, we can begin to look at the C# type system from a broader perspective.
In C#, there are tons of types that have been defined for you. In addition, as you go through this book,
you’ll see that C# gives you the option to create and use your own types.
In this introduction to types, we will cover all of the primitive types, or built-in types. These are types that
the C# compiler knows a whole lot about. The language itself makes it easy to work with these types
because they are so common. We’re going to cover a lot of ground, so as you read this, remember that
you don’t need to master it all in one reading. Get a feel for what types are available, and come back for
the details later.
The ‘int’ Type
We already saw the int type in the last chapter, but it is worth a little more detail here to start off our
discussion on types. The int type uses 4 bytes (32 bits) to keep track of integers. In the math world,
integers can be any size we want. There’s no limit to how big they can be. But on a computer, we need to
be able to store them in bytes, so the int type has to be artificially limited. The int type can store numbers
32
Chapter 6
The C# Type System
roughly in the range of -2 billion to +2 billion. In a practical sense, that’s a bigger range than you will need
for most things, but it is limited, and it is important to keep that in mind.
The ‘byte’, ‘short’, and ‘long’ Types
Speaking of limitations in the size of the int type, there are three other types that also store integers, but
use a different number of bytes. As a result, they have different ranges that they can represent. These are
the byte, short, and long types. The byte type is the smallest. It uses only one byte and can store values
in the range 0 to 255. The short type is larger, but still smaller than int. It uses two bytes, and can store
values in the range of about -32,000 to +32,000. The long type is the largest of the four, and uses 8 bytes.
It can store numbers roughly in the range of -9 quintillion to +9 quintillion. That is an absolutely massive
range, and it is only in very rare circumstances when that wouldn’t be enough.
Type
Bytes
Range
byte
1
0 to 255
short
2
-32,768 to 32,767
int
4
-2,147,483,648 to 2,147,483,647
long
8
-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
That’s four different types (byte, short, int, and long) that use a different numbers of bytes (1, 2, 4, and 8,
respectively). The larger they are, the bigger the number they can store. You can think of these different
types as different sizes of boxes to store different sizes of numbers in.
It is worth a little bit of an example to show you how you create variables with these types. It is done
exactly like earlier, when we created our first variable with the int type:
byte aSingleByte = 34;
aSingleByte = 17;
short aNumber = 5039;
aNumber = -4354;
long aVeryBigNumber = 395904282569;
aVeryBigNumber = 13;
The ‘sbyte’, ‘ushort’, ‘uint’, and ‘ulong’ Types
We’ve already talked about four different types that store integers of various sizes. Now, I’m going to
introduce four additional types that are related to the ones we’ve already discussed.
But first, we need to understand the difference between signed types and unsigned types. If a type is
signed, it means it can include a + or - sign in front of it. In other words, the values of a signed type can be
positive or negative. If you look back at the four types we already know about, you’ll see that short, int,
and long are all signed. You can store positive or negative numbers. The byte type, on the other hand,
has no sign. (It is typically assumed that no sign is the equivalent of being positive.)
For each of the signed types we have looked at, we could have an alternate version where we shift the
valid range to only include positive numbers. This would allow us to go twice as high, at the expense of
not being able to use negative values at all. The ushort, uint, and ulong types do just this. The ushort
type uses two bytes, just like the short type, but instead of going from -32,000 to +32,000, it goes from 0
to about 64,000. The same thing applies to the uint and ulong types.
The ‘char’ Type
33
Likewise, we can take the byte type, which is unsigned, and shift it so that it includes negative numbers as
well. Instead of going from 0 to 255, it goes from -128 to +127. This gives us the signed byte type, sbyte.
Not surprisingly, we can create variables of these types just like we could with the int type:
ushort anUnsignedShortVariable = 59485; // This could not be stored in a normal short.
We now know of eight total types,
all of which are designed to store
only integers. These types, as a
collection, are known as integral
types (sometimes “integer types”).
We can organize these types in the
hierarchy diagram to the right. As
we continue our discussion of types
throughout this chapter and this
book, we’ll continue to expand this chart. You may notice that we’re still missing one additional integral
type, which we’ll discuss shortly.
Each of the types we’ve discussed use a different number of bytes and store different ranges of values.
That means each of them is best suited for storing different types of information. So how do you know
what type to use? Here are three things to consider as you’re choosing a type for a particular variable:



Do you need signed values? (Can things be negative?) If so, you can immediately eliminate all of
the unsigned types.
How large can the values be? For example, if you are using a variable to store the population of a
country, you know you’ll need to use something bigger than a byte or a short.
When in doubt, many programmers tend to default to the int type. This may be sort of a
Goldilocks thing (not too big, not too small, but just about right). As a result, sometimes int is
overused, but it may be a good starting point, at any rate.
The ‘char’ Type
We have one more integral type to discuss before we move on. This last type is called the char type, and
is used for storing single characters or letters, as opposed to numbers.
It is strange to think of characters as an integral type, which are all designed to store integers. Behind the
scenes though, each letter can basically be assigned to a single number, which is why it gets lumped in
among the integral types. However, when we use the char type, the computer will know to treat it as a
letter instead, so we’ll see it print out the letters instead of numbers. As we’ll see later, the close
association of the char type and the other integral types means that we can do most of the same things
with any of these types.
The char type is two bytes big, giving it over 64,000 possible values. It’s enough to store every letter in
every language in the world (including some dead languages and some fictional languages like Klingon)
with room to spare. The numeric value assigned to each letter follows the widely used Unicode system.
You can create variables with the char type in a way that is very similar to the other types we’ve seen,
though you put the character you want to store inside single quotes:
char favoriteLetter = 'c';
favoriteLetter = '&';
// Because C is for cookie. That's good enough for me.
Later in this chapter, we’ll talk about storing text of any length.
34
Chapter 6
The C# Type System
To summarize where we’re at with the C# type system, we’ve learned 9 different integral types, including
four for signed integers, four for unsigned integers, and one for characters:
Type
Bytes
Range
byte
1
0 to 255
short
2
-32,768 to 32,767
int
4
-2,147,483,648 to 2,147,483,647
long
8
-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
sbyte
1
-128 to +127
ushort
2
0 to 65,535
uint
4
0 to 4,294,967,295
ulong
8
0 to 18,446,744,073,709,551,615
char
2
U+0000 to U+ffff
(All Unicode characters)
The ‘float’, ‘double’, and ‘decimal’ Types
You’re probably sick of integral types by now, so let’s expand our discussion to include some additional
types to store what are called floating point numbers (“real numbers” in the math world). Floating point
numbers are able to store not just integer values, but also numbers with a decimal part. For example, the
number 1.2 is not an integer and can’t be stored by any of the integral types that we’ve talked about. But
it can be stored in any of the three floating point types that C# provides.
We’ll start off with the float type. The float type uses four bytes to store floating point numbers. With the
float type, you get seven digits of precision. The largest values you can store are very large: about 3.4 x
1038. This is much higher than any of the integral types. But even these floating point types are limited in
their own way. Because it only has seven digits of precision, the computer can’t tell 1,000,000,000 and
1,000,000,001 apart. (Though it can tell 1000 and 1001 apart.) Of course, maybe you don’t care about that
kind of a difference either, because it’s so small compared to their magnitude. It may be close enough for
all practical purposes. Note that the integral values don’t have this problem. Within the range that they
can store, integral types are always exact.
There is also a limit to how small (as in, “close to zero”) the float type can store, and that is about ±1.5 x
10-45—a very tiny number.
The double type uses 8 bytes—twice as many as the float type (hence the name “double”). This has 15 or
16 digits of precision, and can store values as small as ±5 x 10-324 or as large as ±1.7 x 10308. If you thought
the float type had a huge range, think again. This dwarfs what the float type can hold, albeit at the
expense of taking up twice as much space.
The ‘float’, ‘double’, and ‘decimal’ Types
35
Creating variables with either of these types is pretty straightforward as well:
double pi = 3.14159265358979323846;
float anotherPi = 3.1415926f;
Take a close look at those two numbers. Obviously, the double version has more digits, which it can
handle. But do you see what’s hanging out there at the end of the number we’ve used for the float?
There’s a letter ‘f’.
In code, whenever we write out a value directly it is called a literal. When we say a = 3; the 3 is an integer
literal. Here, when we put 3.1415926, it’s a floating point literal. When we use a floating point literal like
this, the compiler assumes it is the double type. But knowing that the double type is a much bigger “box”
that uses 8 bytes, we can’t just stuff that in a variable that is using the float type. It’s too big. Sticking the
letter ‘f’ on the end of a floating point literal tells the C# compiler to treat is as a literal of the float type,
instead of the double type. Note that we could use lower case ‘f’ or capital ‘F’ and get the same results.
While we’re on the subject, I want to point out that if we’re working with an integer literal, we can put
lower case ‘l’ or upper case ‘L’ on the end to indicate that it is a long integer literal. Though, I’d recommend
that you always use the upper case version (‘L’) because lower case ‘l’ and the number ‘1’ look too much
alike. So you could do this if you want:
long aBigNumber = 39358593258529L;
You are also able to stick a ‘u’ or ‘U’ on the end of an integer literal to show that it is supposed to be an
unsigned integer literal:
ulong bigOne = 2985825802805280508UL;
Anyway, back to the float and double types. Both of these types follow standards that have been around
for decades (see the IEEE 754 floating point arithmetic standard) and are a native part of many computers’
circuitry and programming languages.
C# provides another type, though, that doesn’t conform to “ancient” standards. It’s the decimal type. This
type was built with the idea of using it for calculations involving money. Because it doesn’t have the same
hardware-level support that float and double have, doing math with it is substantially slower, but it
doesn’t have the same issues with losing accuracy that float and double have. This type allows for the
range ±1.0 x 10-28 up to ±7.9 x 1028, but has 28 or 29 significant digits. Essentially, a smaller range overall,
but much higher precision.
You can create variables using the decimal type in a way that is very similar to all of the other types that
we’ve talked about. Similar to the float type, you’ll need to put an ‘m’ or ‘M’ at the end of any literals that
you want to have be treated as the decimal type:
decimal number = 1.495m;
number = 14.4m;
Type
Bytes
Range
Digits of Precision
float
4
±1.0e-45 to ±3.4e38
7
double
8
±5e-324 to ±1.7e308
15-16
decimal
16
±1.0 × 10e-28 to ±7.9e28
28-29
36
Chapter 6
The C# Type System
The ‘bool’ Type
Finally moving away from number-based types, the bool type is used to store Boolean or “truth” values.
They can either be true or false. Boolean values are named after Boolean logic, which in turn was named
after its inventor, George Boole. At first glance, this type may not seem very useful, but in a few chapters
we’ll begin to talk about decision making (Chapter 10) and these will become very important.
You can create or assign values to a variable with the bool type like this:
bool itWorked = true;
itWorked = false;
Note that both true and false are considered Boolean literals.
In many other languages, bool is treated like a special case of an integral type, where 0 represents false
and 1 represents true. (In fact, in many of those languages, 0 represents false and anything but 0 is true.)
While the C# bool type uses 0 and 1 to store true and false behind the scenes, bool values and numbers
are completely separated. For example, you cannot assign 1 to a bool variable, or true to an integer.
While a bool type only keeps track of two states, and could theoretically be stored by a single bit, the bool
type uses up a whole byte, because single bytes are the smallest unit of memory that can be addressed.
The ‘string’ Type
The last type we are going to talk about here is the string type. Behind the scenes, this type is actually
very different from all of the other types that we’ve discussed so far, but we won’t get into the details
about why or how until Chapter 16.
The ‘string’ Type
37
The string type is used to store text of any length. The name “string” comes from the field of formal
languages, where a string is defined as a sequence of symbols chosen from a set of specific symbols. In
our case, that set of symbols is the set of all Unicode characters, which we discussed with the char type.
To create or assign values to string type variables, you follow the same pattern as all of the other types
we’ve seen. String literals are marked with double quotes:
string message = "Hello World!";
message = "Purple Monkey Dishwasher";
When you see this, you’ll probably think back to the Hello World program we made, because we saw a
very similar thing there. It turns out, when we used the line Console.WriteLine("Hello World!"); we were
using a string there as well!
Try It Out!
Variables Everywhere. Create a new console application and in the Main method, create one
variable of every type we’ve discussed here. That’s 13 different variables. Assign each of them a value.
Print out the value of each variable.
In this section, we’ve covered a lot of types, and I know it can be a bit overwhelming. Don’t worry too
much. This chapter will always be here as a reference for you to come back to later.
There are a lot more to types than we’ve discussed here (beyond the built-in types) but this has given us a
good starting point. As we continue to learn C#, we’ll learn even more about the C# type system. The
following diagram shows all of the types we’ve discussed so far. You can see that we’re still missing a few
pieces, but we’ll pick those up as we go.
38
Chapter 6
The C# Type System
Try It Out!
Types Quiz. Answer the following questions to check your understanding. When you’re done, check
your answers against the ones below. If you missed something, go back and review the section that
talks about it.
1.
2.
3.
True/False. The int type can store any possible number.
True/False. The char type is an integral type.
Order the following by how large of numbers they can store, from smallest to largest: short,
long, int, byte.
4. True/False. The byte type is a signed type.
5. Which can store higher numbers, int or uint?
6. What three types can store floating point numbers?
7. Which of the options in question 6 can store the largest numbers?
8. Which of the options in question 6 is considered the most precise?
9. In the following, should myVariable have the type int, char, or string, based on the literal
value on the right hand side? [type] myVariable = "8";
10. What type is used to store true or false values?
Answers: (1) false. (2) true. (3) byte, short, int, long. (4) false. (5) uint. (6) float, double, decimal. (7) double.
(8) decimal. (9) Double quotes indicate a string literal. (10) bool.
Numeric Literal Variations
Earlier, we talked about the various integral types that C# supports, including int, long, uint, ulong, etc.
We also talked about how when we write out a number directly in code, it is called a literal. So for
example, the 3 below is an integral (integer) literal:
int x = 3;
Before we walk away from the subject of different variable types in C#, there are a few nifty tricks that we
can do when defining numeric literals that are worth mentioning.
Scientific Notation or E Notation
In many scientific and mathematical fields, you sometimes deal with gigantic or tiny numbers. As a
programmer, you will sometimes have to deal with these numbers as well. Consider a statement like this:
“There are 602,200,000,000,000,000,000,000 hydrogen atoms in a single gram of hydrogen.”
That very quickly becomes too big to want to write out. So scientists decided they could (and should) use
a different way to write that number out, which is called scientific notation. Using scientific notation, you
would write gigantic or tiny numbers with an exponent instead, and shift the decimal point around.
Instead of that earlier number, you might write 6.022×1023.
Even that isn’t so great in situations where you can’t clearly write an exponent as a superscript (which is
true in code, as well as other places). So in some cases, this same number is written as 6.022e23, or
6.022E23.
We are quickly veering outside of stuff that is truly relevant for this book, but it is worth mentioning that
floating point literals can be written out this way in your C# code, using a small e or a capital E:
double avogadrosNumber = 6.022e23; // The official name of this number.
Depending on the field you’re working in, you may or may not see this much. It’s a different notation that
can take a little getting used to. But I think it’s safe to say that if you’re in a field where this is common,
Numeric Literal Variations
39
you’ll probably get plenty of practice with converting to and from scientific notation, and if you’re not in a
field like this, you probably won’t ever have much of a need to write numbers like this in code.
Binary Literals
Like with scientific notation, there are certain programming scenarios in which it is easier to write out
your numbers in binary than in the normal decimal system.
Like with scientific notation, this doesn’t apply in all fields. You’re more likely to use this if you are dealing
with low-level things like device drivers and parsing network packets than if you’re making a nonnetworked game or a simple data manipulation project. Depending on the type of projects you work on,
you may never use this, or you may use it frequently.
To write a binary literal, start your number with a 0b. For example:
int thirteen = 0b00001101;
There is no requirement to pad the number with leading 0’s, though you are allowed to do so, and is
somewhat common to show full, complete bytes.
Hexadecimal Literals
We’ve now talked about how to write things in base 10, which is the normal format. Base 10 has 10 total
digits (0 through 9). We have also talked about how to write values out that are in base 2, or binary, with
two total digits: 0 and 1.
The final way to write integer literals is by using base 16, or hexadecimal, often just called simply hex. With
a hexadecimal number, you have 16 different digits that you can use. You start at 0, and go through 9 like
the normal, standard decimal system. But then the next number greater than 9 is actually the number A.
Then B. Letters are used through F. At this point, you finally roll over to the next digit.
So for example, to count in hexadecimal (base 16) you would go: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 1A, 1B, 1C, 1D, 1E, 1F, 20, 21… F8, F9, FA, FB, FC, FD, FE, FF, 100, 101…
To write out a literal in hexadecimal, you preface it with 0x.
int theColorMagenta = 0xFF00FF;
This example points out one of the times that hexadecimal is sometimes used: to represent colors. On a
computer, colors are frequently represented as a byte for the red, green, and blue (and sometimes
alpha/transparency) channels. Two hexadecimal “characters” cover a full byte, and so these are frequently
used to indicate how bright or how “on” each color channel is, with 00 being completely off and FF being
completely on.
In addition to colors, hex is also frequently used when working with low-level byte manipulation and
display.
The 0x notation for a hexadecimal number is incredibly common. If you haven’t seen it around before,
keep your eyes open. Now that you know what it is and what to look for, you’ll start seeing it pop up in
random places on your computer.
All integer literals—normal, binary, or hexadecimal—will be treated as an int if the value can fit into an
int, then a uint if that fails, then a long, then a ulong. (You can put the ‘U’ and ‘L’ characters at the end of
any integer literal to automatically promote them to the bigger type.) If you want the literal to fit in a
smaller type, like a byte or a short, you will need to cast to that type (Chapter 9).
40
Chapter 6
The C# Type System
The Digit Separator
The last trick is that of the digit separator. When writing long numbers, humans frequently use a special
separator character to break apart a large number to make it easier to read and understand. For example,
in the USA and other parts of the world, a comma is often used to separate blocks of digits (e.g.,
562,988,142) though other parts of the world use periods (e.g., 562.988.142) or spaces (e.g., 562 988 142).
C# 7 has taken a page from many other languages, and has chosen to use the underscore character (_) to
be a digit separator for your numeric literals:
int bigNumber = 1_000_000_000;
You don’t need do use digit separators. Ever. This is a C# 7 feature, so C# developers went for 15 years
without having this feature and survived. But it can make numbers more readable in some instances.
Interestingly, the digit separator can go almost anywhere in your number. All of the following are allowed:
int a = 123_456_789;
int b = 12_34_56_78_9;
int c = 1_2__3___4____5;
double d = 1_000.000_001;
You can see that you have a lot of flexibility in terms of how you arrange your digit separators. That’s by
design. The purpose of the digits separator is to allow the programmer to make numbers easier to read
and understand. Different scenarios will dictate different schemes.
For example, you could use the digit separator to separate different bytes in a binary literal:
long x = 0b0010010_00100110_00001101_01011111;
Or with the hex literals, you might use the digit separator to demarcate related chunks:
uint color = 0xFF_FF_D1_00;
But there are a few restrictions on where you can place a digit separator. These restrictions generally can
be summed up with the principle that you can’t place them at the start or end of a literal, nor can it be
immediately before or after any special character or symbol within a literal.
These special characters or symbols include the decimal point, the e in exponential notation for floating
point types, the 0x and 0b used for hex and binary literals, or the U’s, L’s, F’s, and M’s that indicate specific
types (unsigned, long, floats, decimals). So none of the following will actually compile:
// COMPILER ERRORS
int a = _1;
int b = 1_;
int c = 0_b_10101;
int d = 0_xFFDDA0;
double e = 1_e3;
float f = 3.14_f;
ulong g = 1_U_L;
double h = 1_.3;
WITH THE DIGIT SEPARATOR!
// Can't start with an underscore.
// Can't end with an underscore.
// Can't place an underscore before or after the 'b' in a binary literal...
// ... or the 'x' in a hex literal.
// Can't place one before or after the 'e' in exponential notation.
// Or around the characters used to mark the type, like the 'f' here...
// ... or the U or L here.
// Can't place an underscore immediately before or after the decimal point.
Type Inference
As we’ve seen, in C#, every variable has a type. This type is a key part of a variable. Things of one type
can’t go into variables of another type without being converted over to it first. Because of that, type
names like int and string will appear all across your code.
Type Inference
41
The C# compiler is very smart, and has a powerful feature within it called type inference. Type inference is
the idea that the compiler can infer or determine the type of something based on clues in the code
around it.
The compiler will use type inference to make your life easier. We’ll see type inference come up again in
the future, but our first encounter is here with the var keyword.
A variable can be declared using the var keyword, instead of one of the other types we’ve talked about.
For example:
var message = "Hello World!";
This code is identical to the version earlier in the chapter that explicitly stated that it was a string.
The compiler is smart enough to pick up on the clues around it that this variable must be a string. In
particular, this line of code is assigning a string literal (“Hello World!”) to the variable, so that really leaves
string as the only option here.
It’s important to point out that var does not mean anything can go in the variable. It is not a catch-all.
Types are important to C#, and var doesn’t erase all of that.
In Visual Studio, in places where you use the var keyword, you can use the mouse and hover over the var.
The popup that appears when you do this will state the type that it has inferred.
There are some definite advantages and drawbacks to var that are worth pointing out. In terms of
advantages, var is a very short name, meaning there are few characters to type. Every other type will be
as long or longer. This is actually magnified greatly when we start defining our own types in a few
chapters. Our own types tend to have longer names. (12 or 20 characters is fairly normal, and 30 or 40 is
not uncommon either.) So the brevity is a big selling point.
The real drawback to var is that it can reduce code clarity, which is a big deal. The var message example
doesn’t illustrate this very well, but there are plenty of scenarios where the compiler can easily infer the
right type, but where the human programmer has a much harder time. Just because the compiler can
infer the correct type does not necessarily mean you should make humans infer it as well.
Different programmers have wildly different takes on if and when to use var. Some programmers feel you
should use var whenever you can. Others banish it from their code entirely. Others will only allow it in
places where a human can quickly and clearly infer the correct type. These are largely personal
preferences. There is not a definitive correct answer.
In this book, I’m going to avoid var when possible. That is because there is no compiler in the book to
help you infer the types. To eliminate confusion, I will use specific named types.
7
7 Basic Math
In a Nutshell










Arithmetic in C# is very similar to virtually every other programming language out there.
Addition: a = 3 + 4;
Subtraction: b = 7 - 2;
Multiplication: c = 4 * 3;
Division: d = 21 / 7;
The remainder operator (‘%’) gets the remainder of a division operation like this: e = 22 % 7; In
this case, the variable e will contain the value 1.
You can combine multiple operations into one, and use other variables too: f = 3 * b - 4;
Operator precedence (order of operations) and operator associativity are rules that govern
which operations happen before others in an expression that contains multiple operations.
These rules can be overridden by grouping things in parentheses.
Compound assignment operators (+=, -=, /=, *=, and %=) do the desired operation and assign
it to the variable on the left. So for instance a += 3; is the same as a = a + 3;
Chapter 9 covers additional math stuff.
With a basic understanding of variables behind us, we’re ready to get in to some deeper material: math.
This math is all pretty easy. The basic arithmetic you learned in elementary school. Computers love math.
In fact, it is basically all they can do. (Well, that and push bytes around.) But they do it incredibly fast.
In this chapter, we’ll cover how to do basic arithmetic in C#. We’ll start with addition and subtraction,
move up through multiplication and division, a lesser known operator called the remainder operator,
positive and negative numbers, operator precedence and associativity, and conclude with some
compound assignment operators, which do math and assign a value all at once.
Operations and Operators
Let’s start with a quick introduction to operations and operators. Whether you know it or not, you’ve been
working with operations since you first learned how to add. In math, an operation is a calculation that
takes two (usually) numbers and does something with them to get a result. Addition is an example of an
Addition, Subtraction, Multiplication, and Division
43
operation, as is multiplication. Operations usually have two pieces to them: an operator (like the ‘+’ sign)
and operands. Operand is just a fancy name for the thing that the operator works on. So if we look at the
operation 2 + 3, the numbers 2 and 3 are operands.
Interestingly, not all operators require two operands. Those that do are called binary operators. These are
probably the ones you are most familiar with. We’ll also see some that only need one operand. These are
called unary operators. C# also has an operator that requires three operands. It’s a ternary operator.
Because of what it does, it won’t make sense to discuss it yet. We’ll look at it in Chapter 10.
Addition, Subtraction, Multiplication, and Division
Doing the basic operations of addition, subtraction, multiplication, and division is pretty straightforward.
Let’s start with a simple math problem. The following code adds the numbers 3 and 4 and stores it into
the variable called a:
int a = 3 + 4;
The same thing works for subtraction:
int b = 5 - 2;
While both of the above examples show doing math on the same line as a variable declaration (which is
why we see the variable’s type listed), all of these operations that we’ll look at can be done anywhere, not
just when a variable is first declared. So this would work too:
int a;
a = 9 - 2;
a = 3 + 3;
// Declaring the variable a.
// Assigning a value to a, using some math.
// Another assignment.
int b = 3 + 1;
b = 1 + 2;
// Declaring b and assigning a value to b all at once.
// Assigning a second value to b.
Remember that a variable must be declared before you can use it, as shown in the above code. You can
also use any previously existing variables in your math. You don’t just have to use numbers:
int a = 1;
int b = a + 4;
int c = a - b;
Don’t rush by that last part too quickly. The fact that you can use variables on the right hand side of an
equation is how you’ll be able to calculate one thing based on another, step-by-step. As your program is
running, the computer will figure out the result of the calculation on the right side of the equals sign and
stick that value in the variable on the left.
You can also chain many operations together on one line:
int result = 1 + 2 - 3 + 4 - 5 + a - b + c - d;
Multiplication and division work in the exact same way, though there’s a few tricks we need to watch out
for if we do division with integers. We’ll look at that more in Chapter 9, but for now, we’ll just use the float
or double type for division instead of integers. The sign for multiplication in C# is the asterisk (*) and the
sign for division in C# is the forward slash (/).
float totalCost = 22.54f;
float tipPercent = 0.18f;
float tipAmount = totalCost * tipPercent;
// Remember, this is the same as 18%
44
Chapter 7
Basic Math
double moneyMadeFromGame = 100000;
double totalProgrammers = 4;
double moneyPerPerson = moneyMadeFromGame / totalProgrammers; // We're rich!
Below is a more complicated example that calculates the area of a circle based on its radius.
// The formula for the area of a circle is pi * r ^ 2
float radius = 4;
float pi = 3.1415926536f; // The 'f' makes it a float literal instead of a double literal.
float area = pi * radius * radius;
// Using the + operator with strings results in "concatenation".
Console.WriteLine("The area of the circle is " + area + ".");
In the above code, we have variables for the radius of a circle, the value of the number π, and the area,
which we calculate using a standard formula from geometry class. We then display the results.
Notice that we can use the ‘+’ operator with strings (text) and numbers. This is a cool feature of C#. It
knows that you can’t technically add a string and a number in a mathematical sense, but there is an
intelligent way of handling it. It knows that it can turn the number into a string (the text representation of
the number) and then it knows of a way to add or combine strings together: concatenation. It sticks one
piece of text at the end of the other to create a new one. If you run this code, you will see that the
program outputs, “The area of the circle is 50.26548.”
By the way, back in Chapter 3, we defined what a statement was. At this point, it is worth defining another
related term: expression. An expression is something that evaluates to a single value. Something like 3 + 4
is an expression, because it can get turned into the value 7. (3 * 2 - 17 + 8 / 4.0) is also an expression. It’s
quite a bit more complex, but it can still be evaluated to a single value. You’ll see as we go through this
book that expressions and statements can get very large.
Try It Out!
Area of a Triangle. Following the example above for calculating the area of a circle, create your own
program that calculates the area of a triangle. The formula for this is:
𝐴=
1
𝑏ℎ
2
A is the area of the triangle, b is the base of the triangle, and h is the height of the triangle.
You will want to create a variable in your program for each variable in the equation. Print out your
results. Run through a few different values for b and h to make sure that it is working. For instance,
check to make sure that if b is 5 and h is 6, the result is 15, and that if b is 1.5 and h is 4, you get 3.
The Remainder Operator
Do you remember in elementary school, not too long after you first learned division, how you always did
things like: “23 divided by 7 is 3 remainder 2”? Well in programming, calculating the remainder has its own
operator. This operator is called the remainder operator, though it is sometimes called the modulo
operator, or mod for short. This operator only gives you back the remainder, not the division result. The
sign for this operator is the percent sign (‘%’). So it is important to remember that in C#, ‘%’ does not
mean “percent,” it means “get the remainder of.”
Unary ‘+’ and ‘-‘ Operators
45
In Depth
Remainders Refresher. I know some people haven’t done much with remainders since elementary
school, so as a refresher, remainders work like this. For 23 % 7, we know that 7 goes in to 23 a total of
3 whole times. But since 7 * 3 is 21, there will be two left over. In this case, 2 is our remainder.)
Say you have 23 apples, and 7 people want the apples, this means everyone gets 3, and there are 2
remaining, or left over. This little example would look like this in C#:
int totalApples = 23;
int people = 7;
int remainingApples = totalApples % people; // this will be 2.
At first glance, the remainder operator seems like an operator that’s not very useful, but if you know how
to use it, you will find ways to put it to use. For example, the remainder operator can be used to
determine if a value is a multiple of another value. If it is, then the remainder operator will give us 0.
int remainder = 20 % 4; // This will be 0, which tells us 20 is a multiple of 4.
Try It Out!
Remainders. Create a simple program to write out the results of a division operation. Create two
integer variables called a and b. Create a third integer variable called quotient (the result of a
division) that stores the result of the division of a and b, and another integer variable called
remainder that stores the remainder (using the % operator). Write out the results using
Console.WriteLine or Console.Write to write out the results in the following form: if a = 17 and b = 4,
print the following:
17/4 is 4 remainder 1
For bonus points check your work. Create another variable and store in it b * quotient + remainder.
Print this value out as well. This value should be the same as a in all cases.
Edit your code to try multiple values for a and b to ensure it’s working like it should.
Unary ‘+’ and ‘-‘ Operators
Let’s now turn our attention to a couple of unary operators. Remember, these only have one operand that
they work on. You’ve already sort of seen these operators, but it is worth going into them a little bit more.
The two operators that we want to talk about here are the unary “+” and unary “-” operators. (We’re using
the same sign as addition and subtraction, but it’s technically a different operator.) You have probably
seen these before in math. They indicate whether the number right after them is positive or negative.
// These are the unary '+' and '-' operators. They only work on the number right after them.
int a = +3;
int b = -44;
// These are the binary '+' and '-' operators (addition and subtraction) that operate on two numbers.
int a = 3 + 4;
int b = 2 - 66;
The unary “+” and unary “-“ indicate the sign of the number (+ or -) that they are in front of. Or looking at it
a different way, the “+” operator will take the value immediately after it and produce a new value that is
exactly equal to it, while the “-“ operator will take the value immediately after it and produce the negative
of it as a result. (So b = -a; takes the value in a, creates a negative version of it, and stores it in b.)
46
Chapter 7
Basic Math
Operator Precedence and Parentheses
Perhaps you remember operator precedence, a.k.a. “order of operations” from math classes. This is the
idea that when many operators appear mixed together in a single expression, some should be done
before the others. This same thing applies in the programming world. All C# operators have a certain
precedence that determine the order you do things in. C# follows the same rules that you learned in
math class. That means, for example, that multiplication, division, and remainder operations happen
before addition and subtraction.
If you want your operations to happen in a different order than the default order of operations, you can
modify it by enclosing the operations you want done first in parentheses. For instance:
// Some simple code for the area of a trapezoid (http://en.wikipedia.org/wiki/Trapezoid)
double side1 = 5.5;
double side2 = 3.25;
double height = 4.6;
double areaOfTrapezoid = (side1 + side2) / 2 * height;
In math, if you have an especially complicated formula with lots of parentheses, sometimes square
brackets (‘[‘ and ‘]’) and curly braces (‘{‘ and ‘}’) are used as “more powerful” parentheses. In C#, like most
programming languages, that’s not how it’s done. Instead, you put in multiple sets of nested parentheses
like below. Be careful with multiple sets of parentheses to ensure everything lines up correctly.
// This isn't a real formula for anything. I'm just making it up for an example.
double a =3.2;
double b = -4.3;
double c = 42;
double d = -91;
double e = 4.343;
double result = (((a + b) * (c - 4)) + d) * e;
Operators also have a concept called associativity which indicate how multiple operations of the same
precedence are grouped together. What value will the variable x below contain?
int x = 5 - 3 - 1; // Is this (5 – 3) – 1`, or 5 – (3 – 1)?
Subtraction is left associative (or left-to-right associative), which means you work from left to right. (5 – 3)
– 1 is the correct way to process this, so x will have a value of 1. The opposite of left associativity is right
(or right-to-left) associativity.
C# stays true to mathematical rules of precedence and associativity, so there aren’t a lot of surprises in
this regard. In the very back of the book, in the Tables and Charts section, the Operators table shows all
C# operators, as well as their precedence and associativity.
Why the ‘=‘ Sign Doesn’t Mean Equals
I mentioned in Chapter 5 that the ‘=‘ sign doesn’t mean that the two things equal each other. Not directly.
Instead, it is an assignment operator, meaning that the stuff on the right gets calculated and put into the
variable on the left side of the ‘=‘ sign.
Watch how this works:
int a = 5;
a = a + 1; // the variable a will have a value of 6 after running this line.
Compound Assignment Operators
47
If you’ve done a lot of math, you may notice how strange that looks. From a math standpoint, it is
impossible for a variable to equal itself plus 1. It’s nonsense.
But from a programming standpoint, it makes complete sense, because what we are really saying is, “take
the value currently in a (5), add one to it (to get 6), and assign that value back into the variable a.” So after
running that line, we’ve taken a and added one to it, replacing the old value of 5.
It’s important to remember that the ‘=’ sign indicates assignment, rather than equality.
Compound Assignment Operators
It turns out that what I just described (taking the current value of a variable, making a change to it, and
updating the variable with the new value) is very common. So much so that there is a whole set of other
operators that make it easy to do this. These operators are called compound assignment operators,
because they perform a math function and assign a value all at once.
For instance, here’s the compound assignment operator that does addition:
int a = 5;
a += 3;
// This is the same as a = a + 3;
There are also equivalent ones for subtraction, multiplication, division, and the remainder operator:
int b = 7;
b -= 3;
//
b *= 5;
//
b /= 4;
//
b %= 2;
//
This
This
This
This
is
is
is
is
the
the
the
the
same
same
same
same
as
as
as
as
b
b
b
b
=
=
=
=
b
b
b
b
*
/
%
3;
5;
4;
2;
At
At
At
At
this
this
this
this
point
point
point
point
b
b
b
b
would
would
would
would
be
be
be
be
4.
20.
5.
1.
We’ve covered a lot of math, and there’s still more to come. But before we dig into even more math, we’re
going to take a little break in the next chapter and do something a little different. We’ll look at how to
allow the user to type in stuff for you to use in your program. We’ll come back and discuss more math
related things in Chapter 9.
8
8 User Input
In a Nutshell


You can get input from the user with Console.ReadLine();
Convert it to the type you need with various methods from the Convert class. For example,
Convert.ToInt32 converts to the int type, Convert.ToDouble converts to the double type,
etc.
In this short chapter, we’ll take a look at two additional pieces of code that we’ll need to create a simple
program that has some actual value.
We’ll first learn how to get input from the user through the console window, then how to convert between
the data types that we learned about in Chapter 6, so that you can change string-based user input to
anything else.
User Input from the Console
We can get input from the user by using a statement like the following:
string whatTheUserTyped = Console.ReadLine();
This looks a lot like Console.WriteLine, which we’ve been using up to this point. Both ReadLine and
WriteLine are methods. Methods are little blocks of reusable code. We’ll get into methods in detail later
(Chapter 15). For now, all we need to know is that we can use them to make things happen—read and
write lines of text in this particular case. Console.ReadLine() will grab an entire line of text from the user
(up until the <Enter> key is pressed) and place it in the whatTheUserTyped variable.
Converting Types
But simply grabbing the user’s input and sticking it in a string variable frequently doesn’t get the input
into the right data type for our needs. For instance, what if we need the user to type in a number? When
we put it into a string, we can’t do math with it. We’ll need to convert it to the right type of data first. To
convert to the right data type, we’re going to use another method that converts any of the built-in data
types to another. For example, to convert this string to an integer, we do this:
A Complete Sample Program
49
int aNumber = Convert.ToInt32(whatTheUserTyped);
There are similar methods for each of the other data types. For instance, ToInt16 is for the type short,
ToBoolean is for the type bool, ToDouble is for double, and so on. The one for the float type is tricky:
ToSingle. Contrasted with the double type, the float type is said to have “single” precision.
These Convert.ToWhatever methods convert things besides just strings. Any of the built-in types can be
converted to any of the other built-in types. For instance, you could do the following to change a double
to a float, or a bool to an int using the code below:
bool b = false;
int i = Convert.ToInt32(b);
double d = 3.4;
float f = Convert.ToSingle(d);
A Complete Sample Program
We now know enough to make a program that does something real: getting information from the user,
doing basic stuff with it, and printing the results. Our next step is to actually make a real program. Even if
you’ve been skipping the Try It Out! sections, take a few minutes and do the one below. While most of
these Try It Out! sections have example code online, as I described in the Introduction, I’ve actually put my
sample code for this particular problem here in the book as well.
Spend some time and actually write the program below. When you’ve got it complete, or if you get hung
up on it, go down and compare what you’ve got with my code below. (Just because yours is different from
the code below, doesn’t mean it is wrong. There are always more than one way to get things done.)
Try It Out!
Cylinders: A Complete Program. We’re going to create a simple program that will tell the user
interesting information about a cylinder. A cylinder is usually defined by a height (h) and radius of the
base (r). Create a program that allows the user to type in these two values. We’ll do some math with
those values to figure out the volume of that cylinder, as well as the surface area. (Don’t worry about
the formulas; I’ll provide them to you.) The program will then output the results to the user.
The formula for the volume of a cylinder is this:
𝑉 = 𝜋𝑟 2 ℎ
The formula for the surface area of a cylinder is this:
𝑆𝐴 = 2𝜋𝑟(𝑟 + ℎ)
In both of these equations, r is the radius of the cylinder, h is the height of the cylinder, and of
course, π≈ 3.1415926.
Once you’ve given the program above a shot, take a look at my code to accomplish this task below:
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace CylinderCalculator
50
Chapter 8
User Input
{
class Program
{
static void Main(string[] args)
{
// Print a greeting message. After all, why not?
Console.WriteLine("Welcome to Cylinder Calculator 1.0!");
// Read in the cylinder's radius from the user
Console.Write("Enter the cylinder's radius: ");
string radiusAsAString = Console.ReadLine();
double radius = Convert.ToDouble(radiusAsAString);
// Read in the cylinder's height from the user
Console.Write("Enter the cylinder's height: ");
string heightAsAString = Console.ReadLine();
double height = Convert.ToDouble(heightAsAString);
double pi = 3.141592654; // We'll learn a better way to do PI in the next chapter.
// These are two standard formulas for volume and surface area of a cylinder.
// You can find them on Wikipedia: http://en.wikipedia.org/wiki/Cylinder_(geometry)
double volume = pi * radius * radius * height;
double surfaceArea = 2 * pi * radius * (radius + height);
// Now we output the results
Console.WriteLine("The cylinder's volume is: " + volume + " cubic units.");
Console.WriteLine("The cylinder's surface area is: " + surfaceArea + " square units.");
// Wait for the user to respond before closing...
Console.ReadKey();
}
}
}
Let’s look at this a little at a time. The first part, with the using directives, is all template code that we
discussed in Chapter 3.
The first thing we add is a simple welcome message to the user.
// Print a greeting message. After all, why not?
Console.WriteLine("Welcome to Cylinder Calculator 1.0!");
This is basically just the Hello World program we did earlier. No surprises here.
Next, we prompt the user to enter the cylinder’s radius and height and turn them into the right data type
using the things we learned in this chapter:
// Read in the cylinder's radius from the user
Console.Write("Enter the cylinder's radius: ");
string radiusAsAString = Console.ReadLine();
double radius = Convert.ToDouble(radiusAsAString);
// Read in the cylinder's height from the user
Console.Write("Enter the cylinder's height: ");
string heightAsAString = Console.ReadLine();
double height = Convert.ToDouble(heightAsAString);
The first line is just simple output. We then read in the user’s text and store it in a string. The third line
uses more stuff we learned in this chapter and turns it into the correct data type. I used the double type,
but float or decimal would accomplish the same task with no meaningful difference. (You would just use
Convert.ToSingle or Convert.ToDecimal instead.) We repeat the process with the cylinder’s height.
Escape Characters
51
We then define the value of PI and use the math operations from the last chapter to calculate the volume
and surface area of the cylinder.
double pi = 3.141592654; // We'll learn a better way to do PI in the next chapter...
// These are two standard formulas for volume and surface area of a cylinder.
// You can find them on Wikipedia: http://en.wikipedia.org/wiki/Cylinder_(geometry)
double volume = pi * radius * radius * height;
double surfaceArea = 2 * pi * radius * (radius + height);
Finally, we output the results to the user, again using stuff from our first Hello World program:
// Now we output the results
Console.WriteLine("The cylinder's volume is: " + volume + " cubic units.");
Console.WriteLine("The cylinder's surface area is: " + surfaceArea + " square units.");
And finally, we end the program by waiting for the user to press any key:
// Wait for the user to respond before closing...
Console.ReadKey();
And there we have it! Our first useful C# program!
Escape Characters
When working with strings, we often run into things called escape characters. Escape characters (or escape
sequences) are special sequences of characters that have a different interpretation than face value. These
escape characters are designed to make it easy to represent things that don’t normally appear on the
keyboard, or that the compiler would otherwise interpret as a part of the language itself.
For example, how could you print out an actual quotation mark character? Console.WriteLine("""); won’t
work. The first quotation mark indicates the beginning of the string, and the compiler will interpret the
middle one as the end of the string. That leaves the third quotation mark, which it can’t make sense of.
To handle this, we can take advantage of escape characters. In C#, you start an escape sequence with the
backslash character (‘\’). Inside of a string literal, you can use a backslash, followed by a quotation mark, to
get it to recognize it as a quotation mark that belongs inside of the string, as opposed to one that
indicates the end of the string:
Console.WriteLine("\"");
It is worth pointing out that even though \" is visually two characters, it is treated as only one.
C# has many different escape characters that you’ll find useful. For example, \t is the tab character, and
\n is the newline character. You can see how this works in the following example:
Console.WriteLine("Text\non\nmore\nthan\none\nline.");
This will print out:
Text
on
more
than
one
line.
What happens if you want to print out an actual backslash character? By default, the computer will try to
take \ and combine it with the next letter to represent an escape character, since it marks the beginning of
an escape sequence. To print out the backslash character, you will need to use two backslashes:
52
Chapter 8
User Input
Console.WriteLine("C:\\Users\\RB\\Desktop\\MyFile.txt");
This will print out:
C:\Users\RB\Desktop\MyFile.txt
If you find that all of these extra slashes are kind of annoying, you can put the ‘@’ symbol before the text
(called a verbatim string literal) which tells the computer to ignore escape characters in the string. The
following line is equivalent to the previous one:
Console.WriteLine(@"C:\Users\RB\Desktop\MyFile.txt");
String Interpolation
It’s common when you display something to the user to mix together text with some code. Earlier in this
chapter, we did this by concatenating the strings and code together with a “+”:
Console.WriteLine("The cylinder's volume is: " + volume + " cubic units.");
C# 6.0 introduced a feature called string interpolation. This feature lets you write this same thing in a
much more concise and readable way:
Console.WriteLine($"The cylinder's volume is: {volume} cubic units.");
String interpolation requires two steps. First, before the string, place a “$”. This tells the C# compiler that
this isn’t just a normal string. It’s one that has code embedded within it that needs to be evaluated. Within
the string, when you want to use code, you simply surround it with curly braces.
This translates to the same code as our first pass, but this is usually more readable, so it’s usually
preferred. The curly braces can contain any valid C# expression. It’s not limited to just a single variable.
That means you can do things like perform some simple math, or call a method, etc. You’ll even get Visual
Studio auto-completion and syntax highlighting inside the curly braces.
There’s obviously a practical limit to how much you can stuff inside the curly braces before it becomes
unreadable. At some point, the logic gets complicated and long enough that you lose track of how it’s
being formatted. At that point, it’s better to pull the logic out onto a separate line and store it in a variable.
9
9 More Math
In a Nutshell








Division with integer types uses integer division; fractional leftovers are discarded.
Casting can convert one type to another type. Many types are implicitly cast from narrow
types to wider types. Wider types can be explicitly cast to certain narrower types using the
type you want in parentheses in front: float a = (float)3.4445;
Division by zero causes an error (Chapter 30) to be thrown for integer types, and results in
infinity for floating point types.
NaN, PositiveInfinity, and NegativeInfinity are defined for the float and double types
(float.NaN or double.PositiveInfinity, for example).
MaxValue and MinValue are defined for essentially all of the numeric types, which indicate
the maximum and minimum values that a particular type can contain.
π and e are defined in the Math class, and can be accessed like this: float area = Math.PI *
radius * radius;
Mathematical operations can result in numbers that go beyond the range of the type the
value is being stored in. For integral types, this results in truncation (and wrapping around).
For floating point types, it results in PositiveInfinity or NegativeInfinity.
You can use the increment and decrement operators (++ and --) to add one or subtract one
from a variable. For example, after the following, a will contain a 4: int a = 3; a++;
We’re back for Round 2 of math. There’s so much math that computers can do—in fact, it is all computers
can do. But this will be our last math-specific chapter. We’ll soon move on to much cooler things.
There are still quite a few things we need to discuss. The things we’re going to talk about in this chapter
are not very closely related to each other. Each section is its own thing. This means that if you already
know and understand one topic, you can just jump down to the next.
Here are the basic things we’re going to discuss in this chapter. We’ll start by talking about doing division
with integers (“integer division”) and an interesting problem that comes up when we do that. We’ll then
talk about converting one type of data to another (called typecasting or simply casting). Next we’ll talk
about dividing by zero, and what happens in C# when you attempt this. We’ll then talk about a few cool
54
Chapter 9
More Math
special numbers in C#, like infinity, NaN, the number e, and π. Then we’ll take a look at overflow and
underflow, and finish up with incrementing and decrementing numbers.
It’s probably not required to read all of this if you’re in a rush, but don’t skip the section on casting or the
section on incrementing and decrementing.
There’s a lot to learn here. Don’t worry if you don’t catch it all your first time. You can always come back
later. In fact, having some experience behind you will probably help make it clearer.
Integer Division
Let’s start this section off with an experiment to illustrate a specific problem that arises when you do
division with integers. In your head, figure out what 7 / 2 is. Got it? Your answer was probably 3.5, or
maybe 3 remainder 1. Both are correct.
Now go into C#, and write the following, and see what ends up in result:
int a = 7;
int b = 2;
int result = a / b;
Console.WriteLine(result);
The computer thinks it is 3! Not 3.5, or 3 remainder 1. What we’re doing isn’t the normal division you
learned in elementary school, but rather, a thing called integer division. In integer division, there’s no such
thing as fractional numbers.
Integer division works by taking only the integer part of the result, leaving off any fractional or decimal
part. This is true no matter how close it is to the next higher number. For example, 99 / 100 is 0, even
though from a math standpoint, it is 0.99, which is really close to 1.
Integer division is used when you do division with any of the integral data types, but not when you use the
floating point types.
For comparison purposes, check out the following piece of code:
int a = 7;
int b = 2;
int c = a / b; // results in 3. Uses integer division.
float d = 7.0f;
float e = 2.0f;
float f = d / e; // results in 3.5. Uses "regular" floating point division.
This gets especially tricky when you mix different data types like this:
int a
int b
float
float
float
= 7;
= 2;
c = 4;
d = 3;
result = a / b + c / d;
Here, the a / b part becomes 3 (like before), but the c / d part does floating point division (the normal
division) and gives us 1.33333. Adding the two gives us 4.33333.
It is important to keep in mind that integer types will always use integer division. This may seem like a
problem, but you can actually leverage integer division to your advantage. The key is to remember when it
happens, so that you aren’t surprised by it.
If you don’t want integer division, you’ll need to convert it to floating point values, as we’ll discuss in the
next section.
Working with Different Types and Casting
55
Working with Different Types and Casting
Typically, when you do math with two things that have the same type (adding two ints for example) the
result is the same type as what you started with. But what if you do math with two different types? For
example, what if you add an int with a long? The computer basically only knows how to do math on two
things with the same type. So to get around this problem, types can be changed to different types on the
fly to allow the operation to be done. This conversion is called typecasting or simply casting.
There are two types of casting in C#. One is implicit casting, meaning it happens for you without you
having to say so, while the other is explicit casting, meaning you have to indicate that you want to do it.
Generally speaking, implicit casting happens for you whenever you go from a narrower type to a wider
type. This is called a widening conversion, and it is the kind of conversion that doesn’t result in any loss of
information. To help explain, remember that an int uses 32 bits, while a long uses 64 bits. Because of this,
the long type can hold all values that the int type can handle plus a whole lot more. Because of this, we
say that the int type is narrower, and the long type is wider. C# will happily cast the narrower int to the
wider long when it sees a need, without having to be told. It will implicitly cast from int to long.
So for instance, you can do the following:
int a = 4049;
long b = 284404039;
long sum = a + b;
When you do the addition operation, the value is pulled from a, implicitly cast into a long, and added to b.
This is a widening conversion. There’s no risk of losing information because the wider type (a long) can
always contain any value of the narrower type (the int). Because there’s no risk of losing data, the
conversion is safe to do.
Floating point types are considered wider than integral types, so when there’s a need, C# will convert
integral types to floating point types. For example:
int a = 7;
float b = 2; // this converts the integer value 2 to the floating point value 2.0
// The value in `a` below will get implicitly cast to a float to do this division because `b` is a float.
// This results in floating point division, rather than integer division.
float result = a / b;
On the other hand, there are also times that we will want to change from a wider type to a narrower type.
This is done with an explicit cast, meaning we need to actually state, “Hey, I want to turn this type into
another type.” Explicit casts usually turn a value from a wider type into a narrower type, which leaves the
possibility of data loss.
To do an explicit cast, you simply put the type you want to convert to in parentheses in front of the value
you want to convert. For instance, look at this example, which turns a long into an int:
long a = 3;
int b = (int)a;
Casting doesn’t just magically convert anything to anything else. Not all types can be converted to other
types. The compiler will give you an error if you are trying to do an explicit cast to something that it can’t
do. If you’re trying to cast from one thing to another and the compiler won’t allow it, the
Convert.ToWhatever() methods might still be able to make the conversion (Chapter 8).
56
Chapter 9
More Math
Casting is considered an operator (the conversion operator) like addition or multiplication, and fits into
the order of operations. Casting has a higher precedence than the arithmetic operators like addition and
multiplication. For example, if you have the following code:
float result = (float)(3.0/5.0) + 1;
The following will happen:
1.
2.
3.
4.
5.
The stuff in parentheses is done first, taking 3.0 and dividing it by 5.0 to get 0.6 as a double.
The conversion/casting will be done next, turning the 0.6 as a double into a 0.6 as a float.
To prepare for addition with 0.6 as a float, and an integer 1, the 1 will be implicitly cast to a float.
Addition will be done with the 0.6 as a float and the 1.0 as a float, resulting in 1.6 as a float.
The 1.6 as a float will be assigned back into the result variable.
Try It Out!
Casting and Order of Operations. Using the above as an example, outline the process that will be
done with the following statements:



double a = 1.0 + 1 + 1.0f;
int x = (int)(7 + 3.0 / 4.0 * 2);
Console.WriteLine( (1 + 1) / 2 * 3 );
If this is confusing, try putting the code into Visual Studio and running it. You can try breaking out
parts of it into different lines and debug it if you want. (Chapter 48 discusses debugging in depth.)
Division by Zero
You probably remember from math class that you can’t divide by zero. It doesn’t make sense
mathematically. Bad things happen. You rip holes in the fabric of space-time, sucking you into a vortex of
eternal doom, where you’re forced to turn trees into toothpicks using nothing but one of those plastic
sporks that you get at fast food restaurants.
So let’s take a moment and discuss what happens when you divide by zero in C#. If you divide by zero
with integral types, a strange thing happens. An exception is “thrown.” That’s a phrase we’ll come back to
in more detail later when we talk about exceptions (Chapter 30), but for now, it is enough to know that an
exception is simply an error. Your program will die instantly if you are running without debugging. On the
other hand, if you are running with debugging, Visual Studio will activate right at the line that the
exception occurred at, allowing you to attempt to fix the problem. (For more information on what to do if
this happens, see Chapter 48.)
Interestingly, if you are using a floating point type like double or float, it doesn’t crash. Instead, you’ll get
the resulting value of Infinity. Integer types don’t define a value that means Infinity, so they don’t have
this option. We’ll look at Infinity and other special numbers in the next section.
Infinity, NaN, e, π, MinValue, and MaxValue
Infinity
There are a few special values that are worth discussing. Let’s start off by looking at infinity. Both the
double and the float type define special values to represent positive and negative infinity (+∞ and -∞). In
the math world, doing stuff with infinity often results in some rather unintuitive situations. For example, ∞
+ 1 is still ∞, as is subtracting 1: ∞ - 1 = ∞.
To use these directly, you can do something like the following:
Overflow and Underflow
57
double a = double.PositiveInfinity;
float b = float.PositiveInfinity;
NaN (Not a Number)
NaN is another special value with the meaning “not a number.” Like infinity, this can come up when you
do something crazy, like ∞/∞. This can be accessed like this:
double a = double.NaN;
float b = float.NaN;
E and π
The numbers e and π are two special numbers that may be used frequently (or not) depending on what
you are working on. Regardless of how often you might use them, they’re worth knowing about. You can
always do what we did a few chapters back, and create a variable to store those values, but why do that
when there’s already a pre-defined variable that does the same thing?
To use these, we’ll use the Math class. (We’re still going to talk about classes a lot more, starting in
Chapter 17.) You can do this with the following code:
double radius = 3;
double area = Math.PI * radius * radius;
// You'll likely find more uses for pi than e.
double eSquared = Math.E * Math.E;
We don’t need to create our own pi variable anymore, because it has already been done for us!
MinValue and MaxValue
Finally, let’s talk quickly about MinValue and MaxValue. Most of the numeric types define a MinValue
and MaxValue inside of them. These can be used to see the minimum or maximum value that the type
can hold. You access these like NaN and infinity for floating point types:
int maximum = int.MaxValue;
int minimum = int.MinValue;
Overflow and Underflow
Think about this. A short can have a maximum value of up to 32767. So what if we do this?
short a = 30000;
short b = 30000;
short sum = (short)(a + b); // The sum will be too big to fit into a short. What happens?
First of all, I should point out that when you do math with the byte or short type, it will automatically
convert them to the int type. So in the code above, I’ve had to do an explicit cast to get it back to a short.
Try out that code, and print out the sum variable at the end and see what you get.
Mathematically speaking, it should be 60000, but the computer gives a value of -5536.
When a mathematical operation causes something to go beyond the allowed range for that type, we get a
thing called overflow. What happens is worth paying attention to. For integer types (byte, short, int, and
long), the most significant bits (which overflowed) get dropped. This is especially strange, because the
computer then interprets it as wrapping around. This is why we end up with a negative value in our
example. You can easily see this happening if you start with the maximum value for a particular type (for
example, short.MaxValue) and adding 1 to it. You’ll end up at the minimum value.
58
Chapter 9
More Math
For the floating point types, things happen differently. Because they have PositiveInfinity and
NegativeInfinity defined, instead of wrapping around, they become infinity.
You actually have more control than what is described above. In Chapter 43 we’ll revisit this and flesh out
overflow a bit more.
Another similar condition called underflow that can occur sometimes with floating point types. Imagine
you have a very large floating point number. Something like 1,000,000,000,000. A float can store that
number. Now let’s say you have a very small number, like 0.00000001. You can store that as a float as
well. However, if you add the two together, a float cannot store the result: 1,000,000,000,000.00000001.
A float just simply cannot be that big and that precise at the same time. Instead, the addition would result
in 1,000,000,000,000 again. This is perhaps close enough for most things. But still, potentially valuable
information is lost. Floating point types have a certain number of digits of accuracy. Starting at the first
digit that matters, it can only keep track of so many other digits before it just can’t keep track of any more.
Underflow is not nearly as common as overflow; you may never encounter a time where it matters.
Incrementing and Decrementing
Perhaps you’ve noticed that lots and lots of times in this book, I’ve added 1 to a value. We’ve already seen
two ways of doing this:
int a = 3;
a = a + 1;
// Normal addition operator, and then assign it to the variable.
And:
int a = 3;
a += 1;
// The compound addition/assignment operator.
Here’s yet another way of adding 1 to a value:
int a = 3;
a++;
This is called incrementing, and “++” is called the increment operator. Before long, we’ll see many places
where we can use this.
As its counterpart, you can use “--” to subtract one from a number. This is called decrementing, and “--” is
called the decrement operator.
Incidentally, the “++” in the name of the well-known C++ programming language comes from this very
feature. C++ was designed to be the “next step” or “one step beyond” the programming language C, hence
the name C++. (C, C++, and Java all have the increment operator too.)
The increment and decrement operators can be written in one of two ways. You can write a++;, or you can
write ++a;. (Likewise, you can write a--; and --a;.) With the ++ at the end, it is called postfix notation, and
with it at the beginning, it is called prefix notation.
There’s a subtle difference between prefix and postfix notation. To understand the difference, it is
important to realize that this operation, like many others, “returns a value.” This is a phrase that we’ll
frequently see later on, but it is a concept we have already been working with. Take the addition operator,
for instance. When we do 2 + 1, the math happens and we’re left with a specific result (3).
As it happens, using the increment or decrement operators will also return a result, aside from just
modifying the variable involved. With postfix notation (a++;) the original value of a is returned. With prefix
notation (++a;) the new, incremented value is returned. Here’s an example:
Incrementing and Decrementing
59
int a = 3;
int b = ++a; // Both 'a' and 'b' will now be 4.
int c = 3;
int d = c++; // The original value of 3 is assigned to 'd', while 'c' is now 4.
a++ means, “give me the value in a, then increment a,” while ++a means, “increment a first, then give me
the resulting value.”
Using this operator to both modify a variable and return a result can be confusing. Even experienced
programmers often don’t readily remember the subtle differences between postfix and prefix notations
without looking it up. It is more common to split the logic across two lines, which sidesteps the confusion:
int a = 3;
a++;
int b = a;
int c = 3;
int d = c;
c++;
10
10 Decision Making
In a Nutshell


Decision making is the ability for a program to make choices and do different things
conditionally.
The if statement is the cornerstone of decision making in C#, and can optionally include else
statements and else-if statements to create more sophisticated behavior:
if(condition)
{
// ...
}
else if(another condition)
{
// ...
}
else
{
// ...
}




In an if statement’s condition block, you can use the operators ==, !=, <, >, <=, and >= to check
if something is “equal to”, “not equal to”, “less than”, “greater than”, “less than or equal to”, or
“greater than or equal to” the second side.
The ! operator reverses Boolean types.
The operators && (and operator) and || (or operator) allow you to check multiple things in an
if statement.
if statements can be nested.
Any program that does real work will have to make decisions. These decisions are made based on
whether a particular condition holds true or not. Sections of code can be ran conditionally. The central
piece of decision making is a special C# statement called an if statement. We’ll start by looking at the if
statement, along with a few related statements. Then we’ll look at a variety of ways to compare two
values. We’ll then look at a few other special operators (“logical operators”) that help us make more
sophisticated conditions.
The ‘if’ Statement
61
The ‘if’ Statement
Imagine a simple scenario where a teacher is assigning grades and wants to know what grade a student
should get based on their test score.
Grading systems vary throughout the world, but a pretty typical one is to give out the letters A, B, C, D,
and F, where A represents a very high level of competence, F represents a failure, and B, C, and D
represent varying levels in between.
The basic process is for the teacher to take a look at the student’s score, and if it’s 90 or higher, the
student gets an ‘A’. If they’re lower than that, but still at 80 or higher, then they get a ‘B’, and so on, down
to a failing grade. In order to figure out what grade a student should get, you need to make decisions
based on some condition. We do things conditionally—meaning it only happens some of the time. We only
give some students A’s, while others get B’s, and others fail.
As we start talking about decision making, let’s go through the process of building a program that will
determine a student’s grade, based on their score.
Let’s start at the beginning. In C#, decision making will always start with an if statement. A simple if
statement can check if two things are equal, like the code below:
if(score == 100)
{
// Code between the curly braces is only executed when the condition in the parentheses is true.
Console.WriteLine("Perfect score!");
}
That should be fairly straightforward. There are a few parts to a basic if statement. We start off by using
the if keyword. Then in parentheses, we put the condition we’re checking for.
There are many ways to define a condition, but for now, we simply use the == operator. The == operator is
called the equality operator. It evaluates to true when the two things on each side are equal to each other.
We then use the curly braces (‘{‘ and ‘}’) to show a code block that should only be run when the condition
is true—the student’s score was 100, in this particular case.
So to put this in the context of a complete program, here is what this might look like in your code:
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace DecisionMaking
{
class Program
{
static void Main(string[] args)
{
// Everything up here, before the if-statement will always be executed.
int score;
Console.WriteLine("Enter your score: ");
string scoreAsText = Console.ReadLine();
score = Convert.ToInt32(scoreAsText);
if (score == 100)
{
// Code between the curly braces is executed when the condition is true.
Console.WriteLine("Perfect score! You win!");
62
Chapter 10
Decision Making
}
// Everything down here, after the if-statement will also always be executed.
Console.ReadKey();
}
}
}
Remember back in Chapter 7, that I said the “=” operator doesn’t mean equals, like we find in math?
Neither does the equality operator (“==”) that we see here, though it is much closer. In math, to say that
two things are equal is an assertion that the two are exactly equivalent to each other, just written in
different forms. The equality operator in C# is a little different, in that it is a check, or a query to see if two
things currently have the same value. If they do, the comparison evaluates to true, and if not, the
comparison evaluates to false.
The ‘else’ Statement
So what if you want to do something in one case, but otherwise, do something else? For instance, what if
you want to print out “You win!” if the student got 100, but “You lose!” if they didn’t? That’s easy to do
using an else block immediately after the if block:
if(score == 100)
{
// This code gets executed when the condition is met.
Console.WriteLine("Perfect score! You win!");
}
else
{
// This code gets executed when it is not.
Console.WriteLine("Not a perfect score. You lose.");
}
The thing to remember here (and use it to your advantage) is that one or the other block of code will be
executed, but not both. If it does one, it won’t do the other, but it will for sure do one of them.
‘else if’ Statements
You can also get more creative with if and else, stringing together many of them:
if(score == 100)
{
Console.WriteLine("Perfect score! You win!");
}
else if(score == 99)
{
Console.WriteLine("Missed it by THAT much."); // Get Smart reference, anyone?
}
else if(score == 0)
{
Console.WriteLine("You must have been TRYING to get that bad of a score.");
}
else
{
Console.WriteLine("Ah, come on! That's just boring. Next time get a more interesting score.");
}
In this code, one (and only one) of the blocks will get executed, based on the score.
While most of the if and else blocks that we’ve looked at so far have had only one statement in them,
that’s not a requirement. You can have as much code inside of each set of curly braces as you want.
Curly Braces Not Always Needed
63
Curly Braces Not Always Needed
So far, our examples have always had a block of code after the if or else statement, surrounded by curly
braces. If you have exactly one statement, then you don’t actually need the curly braces. So we could have
written the previous code like this:
if(score == 100)
Console.WriteLine("Perfect score! You win!");
else if(score == 99)
Console.WriteLine("Missed it by THAT much."); // Get Smart reference, anyone?
else if(score == 0)
Console.WriteLine("You must have been TRYING to get that bad of a score.");
else
Console.WriteLine("Ah, come on! That's just boring. Next time get a more interesting score.");
On the other hand, if you have multiple statements, you will always need the curly braces.
In a lot of cases, if it’s just a single line, it can be more readable to leave off the curly braces, which have a
tendency to add a lot of mental weight to the code. The code feels lighter without them.
It’s important to remember one maintenance problem when you leave off the curly braces. There’s a
danger that as you make changes to your code later on, you might accidentally add in the new lines and
forget to add the curly braces. This completely change the way the code is executed, as shown here:
if(score == 100)
Console.WriteLine("Perfect score! You win!");
Console.WriteLine("That's awesome!");
// Inadvertently executed every time.
Remember, whitespace doesn’t matter in C#, so even though that last statement looks like it belongs in
the if statement, it actually isn’t in there. The middle line will only be executed when the condition is met,
but the last line will always be executed, because it isn’t in the if statement.
Just be careful as you modify if statements so that you don’t make this mistake. If you’re adding a second
line to the the if statement, make sure you’ve also added in the curly braces as well.
Relational Operators: ==, !=, <, >, <=, >=
Let’s take a look at some better and more powerful ways to specify conditions. So far, we’ve only used the
== operator to check if two things are exactly equal, but there are many others.
The == operator that we just saw is one of many relational operators, which is a fancy way of saying “an
operator that compares two things.” There are several others.
For instance, the != operator checks to see if two things are not equal to each other. You use this in the
same way that we use ==, but it does the opposite:
if(score != 100)
{
// This code will be executed, as long as the score is not 100.
}
Then there’s the > and < operators, which determine if something is greater than or less than something
else. These work just like they do in math:
if(score > 90)
{
// This will only be executed if the score is more than 90.
Console.WriteLine("You got an 'A'!");
}
64
Chapter 10
Decision Making
if(score < 60)
{
// This will only be executed if the score is less than 60.
Console.WriteLine("You got an 'F'. Sorry.");
}
Of course, you may have noticed that the code above isn’t exactly what we set out to do in the original
problem statement. We wanted to give an A if they scored at least 90. In the above code, 90 doesn’t result
in an A, because 90 is not greater than 90.
Which brings us to the last two relational operators >= and <=. These two mean “greater than or equal to”
and “less than or equal to” respectively.
if(score >= 90)
{
// This will only be executed if the score is 90 or higher...
// Subtly different than the above example, because it also picks up 90.
Console.WriteLine("You got an 'A'!");
}
In math, we would use the symbols ≤ and ≥, but since those aren’t on the keyboard, C# and most other
programming languages will use <= and >= instead.
This gives us everything we need to write the full program we set out to do:
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace DecisionMaking
{
class Program
{
static void Main(string[] args)
{
int score;
Console.Write("Enter your score: ");
string scoreAsText = Console.ReadLine();
score = Convert.ToInt32(scoreAsText);
//
//
//
if
This if-statement is separate from the rest of them. Not because of the blank
line between this statement and the next block, but because that starts all
over with a brand new if-statement.
(score == 100)
Console.WriteLine("Perfect score! You win!");
//
//
//
if
This checks each condition in turn, until it finds the first one that
is true, at which point, it executes the chosen code block, then jumps down
to after the end of the whole if/else code.
(score >= 90)
Console.WriteLine("You got an A.");
else if (score >= 80)
Console.WriteLine("You got a B.");
else if (score >= 70)
Console.WriteLine("You got a C.");
else if (score >= 60)
Console.WriteLine("You got a D.");
else
Console.WriteLine("You got an F.");
Console.ReadKey();
}
Using ‘bool’ in Decision Making
65
}
}
Using ‘bool’ in Decision Making
Remember when we first discussed data types in Chapter 6 that I told you about the bool type, and that it
would turn out to be useful? We’re there now. You can use variables with the bool type in making
decisions; in fact, it is very common to do so. For instance, check out this block of code, which determines
if a player has enough points to pass the level:
int score = 45; // Ideally, this would change as the player progresses through the game.
int pointsNeededToPass = 100;
bool levelComplete;
if(score >= pointsNeededToPass)
levelComplete = true;
else
levelComplete = false;
if(levelComplete)
{
// We'll be able to do more here later, as we learn more C#
Console.WriteLine("You've beaten the level!");
}
Note that relational operators return or “give back” a bool value—meaning you can use a relational
operator like == or > to directly assign a value to a bool:
int score = 45; // Ideally, this would change as the player progresses through the game.
int pointsNeededToPass = 100;
// The parentheses below are optional.
bool levelComplete = (score >= pointsNeededToPass);
if(levelComplete)
{
// We'll be able to do more here later, as we learn more C#
Console.WriteLine("You've beaten the level!");
}
Try It Out!
Even and Odd. In Chapter 7, we talked about the remainder operator (%). This can be used to
determine if a number is even or odd. If you take a number and divide it by 2 and the remainder is 0,
the number is even. If the remainder is 1, the number is odd. Write a program that asks the user for a
number and displays whether the number is even or odd.
The ‘!’ Operator
For helping with conditions and conditional logic, C# also has the ‘!’ operator, which returns the logical
opposite of what is supplied to it. A true becomes a false, and a false becomes a true. For instance:
bool levelComplete = (score >= pointsNeededToPass);
if(!levelComplete) // If the level is NOT complete...
Console.WriteLine("You haven't won yet. Better keep trying...");
66
Chapter 10
Decision Making
You can also combine this with all of the conditional operators that we’ve talked about, but the ! operator
has higher precedence than the relational operators, so it happens first. If you want to do the comparison
first and then negate it, you have to use parentheses. To illustrate:
if(!(score > oldHighScore))
{
}
// That's effectively the same as:
if(score <= oldHighScore)
{
}
Conditional Operators: && and || (And and Or)
There are a lot of ways your conditions could become more complicated. For instance, imagine you are
making a game where the player controls a spaceship that has both shields and armor, and the player
only dies when both shields and armor are gone. You will need to check both things, rather than just one.
Here’s where conditional operators come in to play. C# has an and operator, which looks like this: &&, and
an or operator that looks like this: ||. (The ‘|’ key is above the <Enter> key on most keyboards and
requires pushing <Shift>.) You can use these to check multiple things at once:
int shields = 50;
int armor = 20;
if(shields <= 0 && armor <= 0)
Console.WriteLine("You're dead.");
This can be read as, “if shields are less than or equal to zero, and armor is less than or equal to zero.”
With the && operator, both parts of the condition must be true in order for the whole expression to be
true.
The || operator works in a similar way, though if either one is true, then the whole thing becomes true.
int shields = 50;
int armor = 20;
if(shields > 0 || armor > 0)
Console.WriteLine("You're still alive! Keep going!");
One thing worth mentioning is that with either of these, the computer will do lazy evaluation. This means
that it won’t check the second part unless it needs to. So in the example above, the computer will always
check to see if shields is greater than 0, but it only bothers checking if armor is greater than 0 if shields is
less than or equal to 0.
You can combine lots of these together, and along with parentheses, make some pretty crazy conditions.
Anything you want is possible, though readability may quickly become an issue. Which leads us to the
next section, which might provide an alternative approach that may be more readable.
Nesting If Statements
You can also put if statements (and if-else statements) inside of other if statements. This is called nesting
them. For example:
if(shields <= 0)
{
if(armor <= 0)
Console.WriteLine("Your shields and armor are both zero! You're dead!");
else
The Conditional Operator ?:
67
Console.WriteLine("Shields are gone, but armor is keeping you alive!");
}
else
{
Console.WriteLine("You still have shields left. The world is safe.");
}
It doesn’t have to end there, either. You could nest if statements inside of if statements inside of if
statements inside of even more if statements. However, the fact that it is doable doesn’t necessarily make
it a good idea. Lots of nesting means the code is less readable. Use it when there’s a need for it (and there
will be) but don’t overdo it.
Try It Out!
Positive or Negative? One thing that a lot of people have trouble with is doing multiplication when
negative numbers are involved. They typically run into trouble when trying to figure out if the result
should be positive or negative.
You’re going to write a program to help them! But there’s a catch. In this example, you’re banned from
actually doing the multiplication to figure out the answer. (It would be all too easy to take two
numbers and multiply them, and then check if the result is greater than or less than zero.) Instead,
you will follow the same logic that a human has to follow to get your answer.
When you’re multiplying two numbers together, if the two numbers have the same sign (both positive
or both negative) the result is positive. If they have different signs, the result is negative.
Write a program that asks the user for two numbers and then, using the rule above, prints out
whether the result should be positive or negative.
The Conditional Operator ?:
Now that we know a little about logic, I want to bring up another operator in C#. Remember when we first
talked about operators in Chapter 7, how we discussed unary and binary operators? Unary operators only
work on one thing, (for example, the negative sign) while binary operators work on two things (addition or
subtraction). I’m going to introduce a new operator, which is a ternary operator. This means it operates on
three parts. This might seem kind of strange to you. It probably ought to. It’s not a very normal thing to
see in math or programming (though this specific operator is in many languages).
This operator is called the conditional operator, and it works quite a bit like an if-else statement. It uses
the ? and : characters like this:
(boolean condition) ? value if true : value if false
A practical example might look like this:
Console.WriteLine((score > 70) ? "You passed!" : "You failed.");
Everything that you do with the conditional operator could have been done without it. But it is a nice
shorthand way to do things that can be very readable. On the other hand, it can also reduce readability,
so it isn’t always a better choice. Just an alternative that you may find helpful.
11
11 Switch Statements
In a Nutshell




switch statements are an alternative to if statements, especially if they involve a lot of “else if
X... else if Y... else if Z...” kind of stuff.
You can use the following types in a switch statement: bool, string, and all integral types.
You can also use enumerations (Chapter 14).
You can’t fall through from one case block to another. You always need a break (or return) at
the end of a block.
In the previous chapter, we looked at basic if statements and decision making. C# has another type of
statement that is very similar to if statements. These statements are called switch statements. In this
case, think of a switch like a railroad switch, which determines which track a train will travelling down.
We’ll take a look at when we’d want to use switch statements, how to do them, and then wrap up with a
couple of extra details about switch statements.
Anything you can do with a switch statement can also be done with an if statement. While there may be
some small performance tradeoffs with using switch vs. if-else (switch is usually considered as fast or
faster, depending on the situation) it is usually not enough to overtake readability concerns. It is usually
best to pick whichever version produces the most readable code.
The Basics of Switch Statements
It is pretty common to have a variable and want to do something different depending on the value of that
variable. For instance, let’s say we have a menu with five choices (1-5). The user types in their choice,
which we store in a variable. We want to do something different depending on which value they chose.
Using only the tools that we already know, we’d probably think about a sophisticated if-else statement. Or
rather, an if/else-if/else-if/else-if/else-if/else statement. Something like this:
int menuChoice = 3;
if (menuChoice == 1)
The Basics of Switch Statements
69
Console.WriteLine("You chose option #1.");
else if (menuChoice == 2)
Console.WriteLine("You chose option #2. I like that one too!");
else if (menuChoice == 3)
Console.WriteLine("I can't believe you chose option #3.");
else if (menuChoice == 4)
Console.WriteLine("You can do better than 4....");
else if (menuChoice == 5)
Console.WriteLine("5? Really? That's what you went with?");
else
Console.WriteLine("Hey! That wasn't even an option!");
That gets the job done, but this could also be done with a switch statement.
To make an equivalent switch statement, we’ll use the switch keyword, and a case keyword for each of
the various “cases” or options that we have. We’ll also use the break and default keywords, but we’ll talk
about those more in a second. The if statement we had above would look like this as a switch statement:
int menuChoice = 3;
switch (menuChoice)
{
case 1:
Console.WriteLine("You chose option #1");
break;
case 2:
Console.WriteLine("You chose option #2. I like that one too!");
break;
case 3:
Console.WriteLine("I can't believe you chose option #3.");
break;
case 4:
Console.WriteLine("You can do better than 4....");
break;
case 5:
Console.WriteLine("5? Really? That's what you went with?");
break;
default:
Console.WriteLine("Hey! That wasn't even an option!");
break;
}
As you can see, we start out with the switch statement, and in parentheses, we put the variable we are
going to base our “switch” on.
Then we have a sequence of case statements (sometimes called case labels), which indicate that if the
variable matches the value in the case statement, the flow of execution enters the following code block.
It is important to note that the flow of execution will go into exactly one of the case labels, so you will
never end up in a situation where more than one case block gets executed.
At the end of each case block, you must put the break keyword, which sends the flow of execution back
outside of the entire switch statement, and down to the next part of the code.
We can also have a default label, which indicates where the flow of execution should go if no other case
label is a match. The default label doesn’t need to be the last one, but it is typical and good practice to do
so. The default block works as a sort of catch-all for anything other than the specific situations of the
other case labels. Note that this is like the final else block in our original, giant if statement.
Switch statements do not allow arbitrary logic in a case condition. That is, with an if statement, we can
write something like if(x < 10), there is no equivalent to this for a switch statement. You can’t write case <
10: or case x < 10:. If you want arbitrary logic, then you will have to fall back to an if statement instead.
70
Chapter 11
Switch Statements
Types Allowed with Switch Statements
In our above example, we see that you can use the int type in a switch statement. Not all types can be
used in a switch, but many can. Here’s the list of types that can be used: bool, string, and any of the
integral types (including char). We haven’t talked about enumerations yet (Chapter 14) but those can also
be used in a switch statement.
No Implicit Fall-Through
C++ and Java have a little trick where if you leave out the break statement, you can have one case block
“fall through” to the next case block. But this is banned in C#:
switch (menuChoice)
// This DOES NOT work in C#
{
case 1:
Console.WriteLine("You chose 1.");
case 2:
Console.WriteLine("You chose 2. Or maybe you chose 1.");
break;
}
Because there’s no break in the case 1 block, the code there would be executed, and then continue on
down into the case 2 block. This isn’t allowed in C#. Every case block needs a break statement.
The reason for requiring this is that people accidentally ended up doing this far more often than they
intentionally do it. They leave off the break statement by accident, resulting in a bug that is usually tricky
to resolve. To prevent this, C# won’t allow you to skip the break statement and fall through.
However, there is one situation where you can do this: multiple case blocks with no code in between it:
switch (menuChoice) // This DOES work in C#
{
case 1:
case 2:
Console.WriteLine("You chose option 1 or 2.");
break;
}
In this case, both 1 and 2 end up in the same case block. While case 1 doesn’t have a break statement, it
also doesn’t need it, because we’re simply stating the two are the equal. You can write the same thing with
an if statement as well, using the || operator.
Try It Out!
Making a Calculator. We’re going to make a simple calculator. Ask the user to type in two numbers
and then to type in a math operation to perform on the two numbers.
Use a switch statement to handle the different operations in different ways. Allow the user to type in
’+’ for addition, ’-’ for subtraction, ’*’ for multiplication, ’/’ for division, and ’%’ for remainder. For bonus
points, allow the user to type in ’^’ for a power. (You can compute this using the Math.Pow method.
For example, the following does x2: Math.Pow(x, 2);)
Print out the results for the user to see.
12
12 Looping
In a Nutshell

while loops, do-while loops, and for loops all allow you to repeat things in various ways.
while(condition) { /* ... */ }
do { /*... */ } while(condition);
for(initialization; condition; update) { /* ... */ }


You can break out of a loop at any time with the break keyword and advance to the next
iteration of the loop with the continue keyword.
The foreach loop is not discussed here, but will be in the next chapter.
In this chapter we’ll discuss yet another very powerful feature in the C# language: loops. Loops allow you
to repeat sections of code multiple times. We’ll discuss three types of loops in this chapter, and cover a
fourth type in the next chapter when we discuss arrays. (It will make a lot more sense there.)
The While Loop
The first kind of loop we’re going to talk about is the while loop. A while loop will repeat certain code over
and over, as long as a certain condition is true. While loops are constructed in a way that looks a whole lot
like an if statement:
while( condition )
{
// This code is repeated as long as the condition is true.
}
Let’s start with a really simple example that counts to ten:
int x = 1;
while(x <= 10)
{
Console.WriteLine(x);
x++;
}
72
Chapter 12
Looping
Let’s take a minute and look at what the computer does when it runs into this code. This starts with the
variable x being 1. The program then checks the condition in the while loop and asks, “is x less than or
equal to 10?” This is true, so the stuff inside the while loop gets executed. It writes out the value of x,
which is 1, then increments x (adds 1 to it). When it hits the end curly brace, it has finished the while loop
and goes back up to the beginning of the loop, checking the condition again. This time however, x has
changed. It is now 2. The condition is still true (2 is still less than or equal to 10) and the loop repeats
again, printing out “2” and incrementing x to 3. This will happen 10 times total, until x gets incremented to
11. When the program checks to see if 11 is less than or equal to 10, the condition is no longer true, and
the flow of execution jumps down past the end of the loop.
One thing to keep in mind is that it is easy to end up with a bug in your code that makes it so the
condition of a loop is never met. Imagine if the x++; line wasn’t there. The program would keep repeating
the loop, and each time x would still be 1. The program would never end! This problem is common
enough to be given its own name: an infinite loop. I promise you, by the time you’re done making your first
real program, you will be the proud writer of at least one infinite loop. It happens.
To fix the problem, you may need to pause your program’s execution, see where it is getting stuck, close
your program, fix the problem in your code, and restart. The process of debugging your program like this
is discussed in Chapter 48.
While we’re on the subject of infinite loops, I’m going to mention that sometimes people make them on
purpose. It sounds strange, I know. But it is easy to do:
while(true)
{
// Depending on what goes in here, you'll never end...
}
Depending on what you put inside the loop (like a break statement, which we’ll discuss in detail later in
this chapter) you can actually still get out of the loop.
I’ve occasionally heard an infinite loop that was done intentionally be called a “forever loop” instead. It’s
not a term all programmers will be familiar with, but there is some value in distinguishing intentional
infinite loops from the accidental ones.
Moving on, here’s a more complicated (and more useful) example which repeats over and over until the
user enters a number between 0 and 10:
int playersNumber = -1;
while(playersNumber < 0 || playersNumber > 10)
{
// This code will get repeated until the player types in a number between 0 and 10.
Console.Write("Enter a number between 0 and 10: ");
string playerResponse = Console.ReadLine();
playersNumber = Convert.ToInt32(playerResponse);
}
One important thing to remember with a while loop is that it checks the condition before even going into
the loop. So if the condition is not met right from the get-go, it doesn’t ever go in the loop. In the example
above, it is important that we initialize playersNumber to -1, because if we had started it at 0, the flow of
execution would have jumped right over the while loop block, and the player would have never been able
to choose a number.
Like with if statements, if you only have one statement inside of a while loop (or any of the other loops
we’ll discuss here) the curly braces are optional.
The Do-While Loop
73
The Do-While Loop
The next type of loop we’ll look at is a slight variation on the while loop. It is the do-while loop.
Remember that a while loop will first check the condition to see if it is met, and if not, it could potentially
skip the loop entirely. (This isn’t a bad thing; it’s just something to remember. Most of the time, that’s
exactly how you want it.)
In contrast, the do-while loop will always be executed at least once. This is useful if you are trying to set
up some stuff the first time through the loop, and you know it needs to be executed at least once.
Let’s revisit that last example, because it is a prime candidate for a do-while loop. Remember, we needed
to set up the player’s number to -1, to force it to go through the loop at least once? Doing this as a dowhile loop solves the need for that:
int playersNumber;
do
{
Console.Write("Enter a number between 0 and 10: ");
string playerResponse = Console.ReadLine();
playersNumber = Convert.ToInt32(playerResponse);
}
while (playersNumber < 0 || playersNumber > 10);
To form a do-while loop, we put the do keyword at the start of the loop, and at the end, we put the while
keyword. Also notice that you need a semicolon at the end of the while line. Everything else is the same,
but this time, you don’t need to initialize the playersNumber variable because we know that the loop will
be executed at least once before the condition is checked.
The For Loop
Now let’s take a look at a slightly different kind of loop: the for loop. For loops are very common in
programming. They are an easy way of doing counting-type loops, and we’ll see how useful they are again
in the next chapter with arrays. For loops are a bit more complicated to set up, because they require three
components inside of the parentheses, whereas while and do-while loops only require one. It is
structured like this:
for(initial condition; condition to check; action at end of loop)
{
//...
}
There are three parts separated by semicolons. The first part sets up the initial state, the second part is
the condition (the same thing that was in the while and do-while loops), and the third part is an action
that is performed at the end of the loop. An example will probably make this clearer, so let’s do the
counting to ten example as a for loop:
for(int x = 1; x <= 10; x++)
{
Console.WriteLine(x);
}
Note that we can declare and initialize a variable right in the for loop like we do here, with int x = 1;.
One of the reasons why this kind of loop is so popular is because it separates the looping logic from what
you’re actually doing with the number. Rather than having the x++ statement inside of the loop and
74
Chapter 12
Looping
declaring the variable before the loop, all of that stuff gets packed into the loop control mechanism,
making the stuff you’re actually trying to accomplish clearer.
One other cool thing to point out is that anything you can do with one type of loop you can do with the
other two as well. Often, one type of loop is cleaner and easier to understand than the others, but they
can all do the same stuff. If suddenly the C# world ran out of while keywords, and all you had left was
fors, you’d be fine. Because of this, you should pick the looping mechanism that creates the code that is
easiest to understand.
Breaking Out of Loops
Another cool thing you can do with a loop is break out of it any time you want. Sometimes, as you’re
working through a loop, you get to a point where you know there’s no point in continuing with the loop.
You can jump out of a loop whenever you want with the break keyword like this:
int numberThatCausesProblems = 54;
for(int x = 1; x <= 100; x++)
{
Console.WriteLine(x);
if(x == numberThatCausesProblems)
break;
}
This code will only go until it hits 54, at which point the if statement catches it and sends it out of the loop.
This is not a very practical example, but it is a good illustration of how you might use the break
command. When we start the loop, we’re fully expecting to get to 100, but then we realize at some point
that there’s a critical problem (or alternatively, we’ve found the result we were looking for) and we can
ignore the rest of the loop.
By the way, this is the kind of thing that makes forever loops actually worth something:
while(true)
{
Console.Write("What is thy bidding, my master? ");
string input = Console.ReadLine();
if(input == "quit" || input == "exit")
break;
}
Before we leave the topic of forever loops, I should say that you rarely truly need to write one. We could
have restructured the code above to not need it. (By moving the input variable outside of the loop and
converting the if statement to be the condition in the while loop.) But occasionally, the code will be more
readable this way, so it is good to know about.
Continuing to the Next Iteration of the Loop
Similar to the break command, there’s another command that, rather than getting out of the loop
altogether, jumps back to the start of the loop and checks the condition again. In other words, it
continues on to the next iteration of the loop without finishing the current one.
This is done with the continue keyword:
for(int x = 1; x <= 10; x++)
{
if(x == 3)
continue;
Nesting Loops
75
Console.WriteLine(x);
}
In this code sample, all of the numbers will get printed out with one exception. 3 gets skipped because of
the continue statement. When it hits that point, it jumps back up, runs the x++ part of the loop, checks
the x < 10 condition again, and continues on with the next cycle through the loop.
Nesting Loops
Like with if statements it is possible to nest loops. And to put if statements inside of loops and loops
inside of if statements. You can go absolutely crazy with all of this control!
Let’s go through a couple more complete examples.
I’m going to repeat a really simple example that I remember from when I first started learning to program.
(Back then, it was C++, not C#, and we had to walk uphill both ways to school, and define our own true
and false!) The task was to write a loop that would print out the following (you were only allowed to have
the ‘*’ character in your program once):
**********
**********
**********
**********
**********
The code below accomplishes this:
for(int row = 0; row < 5; row++)
{
for(int column = 0; column < 10; column++)
Console.Write("*");
Console.WriteLine(); // This makes it wrap around to the beginning of the line.
}
Let’s try a harder one. If we want to do this:
*
**
***
****
*****
******
*******
********
*********
**********
The code would be:
for(int row = 0; row < 10; row++)
{
for(int column = 0; column < row + 1; column++)
Console.Write("*");
Console.WriteLine();
}
Notice how tricky we were, using row in the condition of the for loop with the column variable.
76
Chapter 12
Looping
I should probably mention (because I’m sure you’re wondering) that programmers seem to love 0-based
indexing, meaning we love to start counting at 0. (There’s actually a good reason for this, which we’ll look
at a little bit more when we look at arrays in the next chapter.)
You’ll see that I’ve done this frequently in these samples. I start with row and column at 0, and go up to
the amount I want (10 in this case), but not including it. That does it 10 times. You could do it starting at 1,
and use row <= 10 instead, but what I wrote is more typical of a programmer.
Try It Out!
Print-a-Pyramid. Like the star pattern examples that we saw earlier, create a program that will print
the following pattern:
*
***
*****
*******
*********
If you find yourself getting stuck, try recreating the two examples that we just talked about in this
chapter first. They’re simpler, and you can compare your results with the code included above.
This can actually be a pretty challenging problem, so here is a hint to get you going. I used three total
loops. One big one contains two smaller loops. The bigger loop goes from line to line. The first of the
two inner loops prints the correct number of spaces, while the second inner loop prints out the
correct number of stars.
Try It Out!
FizzBuzz. We now know everything we need to be able to do a popular test problem in programming:
the FizzBuzz problem. This is a simple little toy problem that many people who claim to know a
particular programming language still seem to struggle with.
If you can complete this problem, you’re probably better off than half of the other people in the
world who claim to know C#. It’s actually a sad state of affairs, because the problem is so simple, but
even still, it is a fact than many people who claim to know C# (or another language) can’t even write
this simple program in it. (Admittedly, they are frequently asked to do this on a whiteboard in a job
interview and don’t have a compiler to check their work, which makes the problem harder.)
The challenge is to print out all of the numbers from 1 to 100. Except if a number is a multiple of 3,
print out the word “Fizz” instead. If the number is a multiple of 5, print out “Buzz”. If a number is a
multiple of both 3 and 5 (like 15 or 30) then print out “FizzBuzz”. For example, “1 2 Fizz 4 Buzz Fizz 7 8
Fizz Buzz…”
A couple of things to remember that seem to derail people: Be sure you’re going through the
numbers 1-100, not 0-99, and you’ll likely use the remainder operator (%) which can be used to
determine if something is a multiple of another number.
Still to Come: Foreach
It is worth mentioning that we have one more type of loop to discuss, which we’ll do in the next chapter:
the foreach loop. I only bring that up because a full discussion of loops has to include this type of loop.
But the foreach loop makes the most sense in conjunction with arrays, which we’ll be talking about next.
13
13 Arrays
In a Nutshell









Arrays store collections of related objects of the same type.
To declare an array, you use the square brackets: int[] numbers;
To create an array, you use the new keyword: int[] scores = new int[10];
You can also use collection initializer syntax: int[] scores = new int[] { 1, 2, 3, 4, 5 };
You can access and modify values in an array with square brackets as well:
int firstScore = scores[0]; and scores[0] = 44;
Indexing of arrays is 0-based, so 0 refers to the first element in the array.
You can create arrays of anything, including arrays of arrays: int[][] grid;
You can also create multi-dimensional arrays: int[,] grid = new int[5, 4];
You can use the foreach loop to easily loop through an array and do something with each
item in the array: foreach(int score in scores) { /* ... */ }
In this chapter, we’ll take a look at arrays. Arrays are a variable type that allows us to group multiple items
of the same type together into a single collection. This chapter discusses how to make and use arrays, a
few samples of using them, how to make multi-dimensional arrays, and then wrap up with a discussion of
the last type of loop that we didn’t discuss in the previous chapter.
What is an Array?
An array is a way of keeping track of a group of many related things of the same type in a single collection.
Imagine that you have a high score board in a game, with 10 high scores on it. Using only the tools we
know so far, you could create one variable for every score that you want to keep track of. For example:
int score1 = 100;
int score2 = 95;
int score3 = 92;
// Keep going to 10...
That’s one way to do it. But what if you had 10,000 scores? All of a sudden, creating that many variables to
store scores becomes overwhelming.
78
Chapter 13
Arrays
This brings us to arrays. Arrays are perfect for keeping track of things like this, since they could store 10
scores or 10,000 scores in a way that is both easy to create and easy to work with.
Creating Arrays
Declaring an array is very similar to declaring any other variable. You give it a type and a name, and you
can initialize it at the same time if you want. You declare an array using square brackets ([ and ]).
int[] scores;
The square brackets here indicate that this is an array, not just a single int. This declares an array variable
which can contain multiple ints. Like with all other types that we’ve discussed so far, this is basically just a
named location in memory to stick stuff. To actually create a new array and place it in the variable, use the
new keyword and specify the number of elements that you’ll have in the array:
int[] scores = new int[10];
In the first example, we had not yet assigned a value to the array variable. We had just declared it,
reserving a spot for it. In this second example, our array now exists and has room for 10 items in it.
Once we have set a size for an array, we can’t change it. You can change the individual values in the array
(we’re still coming to that) but not the size. You can, however, create a new array with a different size, copy
the values over into part of the new, larger array, and then assign that back in to your array variable. The
variable itself doesn’t care how large the array is specifically.
On the other hand, if you truly want resizable collections, you should use the List type, which we’ll talk
about in Chapter 25.
By the way, this is the first time we’re seeing the new keyword, but it won’t be the last. We use this
keyword to create things that aren’t just simple primitive data types. We’ll see this come back in a big way
later, when we start looking at classes in Chapter 17.
Arrays aren’t limited to ints. You can make arrays of anything. For example, here’s an array of strings:
string[] names = new string[10];
(For those coming from a C++ background, you may be wondering if there is a delete keyword, and when
and how to clean things up. While C++ requires you to clean up memory that you are no longer using with
delete, C# doesn’t require or even allow this. C# uses managed memory, and uses garbage collection to
clean up things that are no longer in use. We’ll look at this in more detail in Chapter 16.)
Getting and Setting Values in Arrays
Now that we have an array declared and created, we can assign values to specific spots in the array. To
access a specific spot in the array, we use the square brackets again, along with what is called a subscript
or an index, which is a number that tells the computer which of the elements (things in an array) to access.
For example, to put a value in the first spot of an array, we would use the following:
scores[0] = 99;
You can see here that the first spot in the array is number 0. It is very important to remember that array
indexing is 0-based. If you forget about 0-based indexing, and start with 1, you’ll run into what are called
“off-by-one” errors, because you’re on the wrong number.
You can access any value in an array by using the same technique:
More Ways to Create Arrays
79
int fourthScore = scores[3];
int eighthScore = scores[7];
If you try to access something beyond the end of the array (for example, scores[54] in an array with only
10 items in it), your program will crash, telling you the index was out of the bounds of the array.
Side Note
0-based Indexing. I know it seems kind of strange if you’re new to programming, but there’s a very
good reason for 0-based indexing. The array itself has a specific spot in memory—a specific memory
address. This address is called the base address.
For any particular array, the size of each slot in the array may be a different size based on the size of
the type you’re putting into the array. An array of ints will have slots that are 4 bytes big, since ints
are 4 bytes. An array of longs will have slots that are 8 bytes big.
Because the computer knows the base memory address, and how big each slot is, it is easy for it to
calculate the memory location of any particular index in the array using the simple formula:
𝑎𝑑𝑑𝑟𝑒𝑠𝑠 = 𝑏𝑎𝑠𝑒 + 𝑖𝑛𝑑𝑒𝑥 × 𝑠𝑖𝑧𝑒
If we didn’t use 0-based indexing (perhaps using 1-based indexing instead) the math here would
become more complicated.
More Ways to Create Arrays
There are a couple of other ways of initializing an array that are worth discussing here. You can also
create an array by giving it specific values right from the get-go, by putting the values you want inside of
curly braces, separated by commas:
int[] scores = new int[10] { 100, 95, 92, 87, 55, 50, 48, 40, 35, 10 };
When you create an array this way, you don’t even need to state the number of items that will be in the
array (the ‘10’ in the brackets, in this case). You can leave that out:
int[] scores = new int[] { 100, 95, 92, 87, 55, 50, 48, 40, 35, 10 };
In fact, because of type inference, in these cases, you can even leave off the type name as long as all of
the items are the same literal type:
int[] scores = new [] { 100, 95, 92, 87, 55, 50, 48, 40, 35, 10 };
Array Length
We can tell how long an array is by using the Length property. We haven’t talked about properties at all
yet (we will cover them in depth in Chapter 19), but the code for doing this is useful enough and simple
enough that it is worth taking the time to point out. For instance, the code below grabs an array’s Length
property and uses it to print out the length of an array:
int totalThingsInArray = scores.Length;
Console.WriteLine("There are " + totalThingsInArray + " things in the array.");
Some Examples with Arrays
Now that we know the basics of how arrays work, let’s look at a couple of examples using arrays.
80
Chapter 13
Arrays
Minimum Value in an Array
In our first example, we’ll calculate the minimum value in an array, using a for loop.
The basic process will require looking at each item in the array in turn. We’ll create a variable to store the
value that we know is the minimum that we’ve seen so far. As we go down the array, if we find one that is
less than our current known minimum, we update the known minimum to the new one.
int[] array = new int[] { 4, 51, -7, 13, -99, 15, -8, 45, 90 };
int currentMinimum = Int32.MaxValue; // We start high, so that any element in the array will be lower.
for(int index = 0; index < array.Length; index++)
{
if(array[index] < currentMinimum)
currentMinimum = array[index];
}
// At this point, currentMinimum contains the minimum value in the array.
Average Value in an Array
Let’s try a similar but different task: finding the average value in an array. We’ll follow the same basic
pattern as in the last example, but this time we’re going to total up the numbers in the array. When we’re
done doing that, we’ll divide by the total number of things in the array to get the average.
int[] array = new int[] { 4, 51, -7, 13, -99, 15, -8, 45, 90 };
int total = 0;
for (int index = 0; index < array.Length; index++)
total += array[index];
float average = (float)total / array.Length;
Try It Out!
Copying an Array. Write code to create a copy of an array. First, start by creating an initial array. (You
can use whatever type of data you want.) Let’s start with 10 items. Declare an array variable and
assign it a new array with 10 items in it. Use the things we’ve discussed to put some values in the
array.
Now create a second array variable. Give it a new array with the same length as the first. Instead of
using a number for this length, use the Length property to get the size of the original array.
Use a loop to read values from the original array and place them in the new array. Also print out the
contents of both arrays, to be sure everything copied correctly.
Arrays of Arrays and Multi-Dimensional Arrays
You can have arrays of anything. ints, floats, bools. Even arrays of arrays! This is one way that you can
create a matrix. (A matrix is simply a grid or table of numbers with rows and columns.)
To create an array of arrays, one option is to use the following notation:
int[][] matrix = new int[4][];
matrix[0] = new int[4];
matrix[1] = new int[5];
matrix[2] = new int[2];
matrix[3] = new int[6];
The ‘foreach’ Loop
81
matrix[2][1] = 7;
Notice that each of my arrays within the main array has a different length. You could of course make them
all the same length (there’s a better way to do this that we’ll see in a second). When each array within a
larger array has a different length, it is called a jagged array. If they’re all the same length, it is often called
a square array or a rectangular array.
There’s another way to work with arrays of arrays, assuming you want a rectangular array (which is often
the case). This is called a multi-dimensional array.
To do this, you put multiple indices inside of one set of square brackets like this:
int[,] matrix = new int[4, 4];
matrix[0, 0] = 1;
matrix[0, 1] = 0;
matrix[3, 3] = 1;
It is worth briefly describing how you might go about looking at each element in these more complicated
arrays. For an array of arrays, or a jagged array, this might look like this:
int[][] matrix = new int[4][];
matrix[0] = new int[2];
matrix[1] = new int[6];
// Continue filling in values for the jagged array...
for(int row = 0; row < matrix.Length; row++)
{
for(int column = 0; column < matrix[row].Length; column++)
Console.Write(matrix[row][column] + " "); // Each item in the row separated by spaces
Console.WriteLine(); // Rows separated by lines
}
Or with a multi-dimensional array:
int[,] matrix = new int[4,4];
// Fill in contents for multi-dimensional array
// Note: GetLength gives back the size of the multi-dimensional array for a specific index.
for(int row = 0; row < matrix.GetLength(0); row++)
{
for(int column = 0; column < matrix.GetLength(1); column++)
Console.Write(matrix[row, column] + " ");
Console.WriteLine();
}
The ‘foreach’ Loop
To wrap up our discussion of arrays, let’s go back to what was mentioned in the last chapter about loops.
There’s one final type of loop that works really well when working with collections such as arrays. This
type of loop is called the foreach loop (as in, “do this particular task for each element in the array”).
To use a foreach loop, you use the foreach keyword with an array, specifying the name of the variable to
use inside of the loop:
int[] scores = new int[10];
foreach (int score in scores)
Console.WriteLine("Someone had this score: " + score);
82
Chapter 13
Arrays
Inside of the loop, you can use the score variable. One key thing to note is that inside of the loop, you
have no way of knowing what index you are currently at. (You don’t know if you are on scores[2] or
scores[4].) In many cases, that’s no big deal. You don’t care what index you’re at, you just want to do
something with each item in the array.
If you need to know what index you’re at, your best bet is to use a for loop instead:
int[] scores = new int[10];
for(int index = 0; index < scores.Length; index++)
{
int score = scores[index];
Console.WriteLine("Score #" + index + ": " + score);
}
Compared to a for loop, a foreach loop has a tendency to be more readable. There is not as much
overhead clutter inside of the parentheses. Also, in many cases the first thing you do a for loop is use the
index to get the correct item out of the array. This is bypassed with a foreach loop.
But foreach loops are somewhat slower, simply as a result of their internal mechanics. If you’re
discovering that a particular foreach loop is taking way more time than you’d like, converting it to a for
loop is an easy way to get a little extra speed out of it.
This leaves us with a final question: when can foreach loops be used? The short answer (which won’t
make much sense just yet) is that it can be used on anything that implements the IEnumerable interface.
We haven’t talked about interfaces at all yet, but virtually every container or collection across the .NET
Platform uses it. That means that foreach can be used on almost anything that has multiple items in it.
Try It Out!
Minimum and Averaging Revisited. Earlier in this chapter, I presented code to go through an array
and find the minimum value contained in it. I also presented code to average the values in an array.
Using that code as a starting point, rewrite them to use foreach loops instead.
14
14 Enumerations
In a Nutshell





Enumerations are a way of defining your own type of variable so that it has specific values.
Enumerations are defined like this: public enum DaysOfWeek { Sunday, Monday, Tuesday,
Wednesday, Thursday, Friday, Saturday };
Enumerations are usually defined directly in a namespace, outside of any classes or other
type definitions.
The fact that an enumeration can only take on the values that you explicitly listed means that
you won’t end up with bad or meaningless data (unless you force it by casting).
You can assign your own numeric values to the items in an enumeration: public enum
DaysOfWeek { Sunday = 5, Monday = 6, Tuesday = 7, Wednesday = 8, Thursday = 9, Friday
= 10, Saturday = 11 };
Enumerations (or enumeration types, or enums for short) are a cool way to define your own type of variable.
This is the first of many ways we’ll see to create your own.
The word enumeration comes from the word enumerate, which means “to count off, one after the other,”
which is the intent of enumerations. We’ll start by looking at the kinds of situations where enumerations
might be useful. We’ll then look at how to create your own enumeration and use it. We’ll take a look at a
few additional important features of enumerations before wrapping up the chapter.
The Basics of Enumerations
Let me start off with an example of when enumerations may be used. Let’s say you are keeping track of
something that has a known, small, specific set of possible values that you can use—for instance, the days
in the week. One way you could do this is to assign numbers to each of these in your head. Sunday would
be 1, Monday would be 2, Tuesday would be 3, and so on. Your code could look something like this:
int dayOfWeek = 3;
if(dayOfWeek == 3)
Console.WriteLine("It's Tuesday!");
84
Chapter 14
Enumerations
For the sake of readability, you might decide to create a variable to store each of these days:
int
int
int
int
int
int
int
sunday = 1;
monday = 2;
tuesday = 3;
wednesday = 4;
thursday = 5;
friday = 6;
saturday = 7;
int dayOfWeek = tuesday;
if(dayOfWeek == tuesday)
Console.WriteLine("It's Tuesday!");
This kind of situation is exactly what enumerations are for. You create an enumeration, and list the
possible values it can have.
When we create an enumeration, we’re defining a new type. We’ve discussed a wide variety of types
before, but this is the first experience we’ll have creating our own type. When we define a new type, we
put them directly inside of the namespace. You use the enum keyword, give your enumeration a name,
and list the values that your enumeration can have inside of curly braces:
public enum DaysOfWeek { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };
Remember, enumerations are placed directly inside of the namespace (outside of other classes or types
you might have, including the Program class that we’ve been using). This is shown in context below:
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace Enumerations
{
// You define enumerations directly in the namespace.
enum DaysOfWeek { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };
public class Program
{
static void Main(string[] args)
{
}
}
}
Alternatively, enumerations can be defined in their own file. We’ll see how to do this when we learn about
classes in Chapter 18.
Inside of your Main method, you can now create variables that have your new DaysOfWeek type, instead
of just the built-in types that we’ve been working with. We’ve defined a brand new type! Creating a
variable with your new type works just like any other variable:
DaysOfWeek today; // Indicate the type, and give it a name.
To assign it a value, you will use the ‘.’ operator (usually read “dot operator”, but more formally called the
“member access operator”). We’ll discuss the ‘.’ operator more later, but for now what you need to know is
that it is used for member access. This means that you use the ‘.’ operator to use something that is a part
of something else. The values are a part of the enumeration, so we use this operator.
today = DaysOfWeek.Tuesday;
Why Enumerations are Useful
85
This works like any other variable, even for things like comparison in an if statement:
DaysOfWeek today = DaysOfWeek.Sunday;
if(today == DaysOfWeek.Sunday)
{
// ...
}
Why Enumerations are Useful
There’s a very good reason to use enumerations. Let’s go back to where we first started this chapter—
doing days of the week with the int type. Remember how we had defined dayOfWeek = 3; which meant
Tuesday? What if someone put dayOfWeek = 17;? As far as the computer knows, this should be valid.
After all, dayOfWeek is just an int variable. Why not allow it to be 17? But for what we’re doing, 17 doesn’t
make any sense. We are only using 1 through 7.
Enumerations force the computer (and programmers) to use only specific, named (“enumerated”) values.
It prevents tons of errors, and makes your code more readable. For example, dayOfWeek =
DaysOfWeek.Sunday is immediately clear what it means, while dayOfWeek = 1 is not.
Underlying Types
Before moving on, it is worth looking at a couple of additional details about enumerations. Under the
surface, C# is simply wrapping our enumeration around an underlying type. By default, this underlying
type is the int type, though it is possible to choose a different integer type. Enumerations are, at their
core, just numbers, though done in a way that ensures that only the right numbers can be used. When
you create an enumeration, each item you list is assigned a number, starting at 0. In the enumeration that
we created, Sunday has a value of 0, Monday is 1, and so on.
This means that you can cast to and from an int like this:
int dayAsInt = (int)DaysOfWeek.Sunday;
DaysOfWeek today = (DaysOfWeek)dayAsInt;
// Both of these require an explicit cast.
By default, enumerations make sure that only valid values are used. However, if you use an explicit cast to
convert an invalid number to an enumeration, you can break this:
DaysOfWeek today = (DaysOfWeek)17; // Legal, but a bad idea...
Because of this, you should be very cautious about casting to an enumeration type.
Assigning Numbers to Enumeration Values
When you create an enumeration, you can additionally assign it specific values if you want:
enum DaysOfWeek { Sunday=5, Monday=6, Tuesday=7, Wednesday=8, Thursday=9, Friday=10, Saturday=11 };
Try It Out!
Months of the Year. Using the DaysOfWeek enumeration as an example, create an enumeration to
represent the months of the year. Assign them the values 1 through 12. Write a program to ask the
user for a number between 1 and 12. Check to be sure that they gave you a value in the right range
and use an explicit cast to convert the number to your month enumeration. Then, using a switch
statement or if statement to print out the full name of the month they entered.
15
15 Methods
In a Nutshell





Methods are a way of creating a chunk of reusable code.
You can return stuff from a method by putting the return type in the method definition, and
then using the return keyword inside of the method anywhere you want to return a value.
Parameters are passed to a method by listing them in parentheses after the method’s name.
Methods can share the same name (“overloading”) as long as their signatures are different.
Recursion is where you call a method from inside itself, and requires a base case to prevent
the flow of execution from continuing into deeper and deeper method calls.
Think about how much code goes into a program like Microsoft Word or a game like the latest Assassin’s
Creed. They’re enormous! Especially when compared to the little programs that we’ve been making so far.
How do you think you could manage all of that code? Where would you look if there was a bug?
As programmers, we’ve learned that when you have a large program, the best way to manage it is by
breaking it down into smaller, more manageable pieces. This even gives us the ability to reuse these
pieces. This basic concept of breaking things down into smaller pieces is called divide and conquer, and it
is one we’ll see over and over again.
C# provides us with a feature that allows us to break down our program into small manageable pieces.
These small, reusable building blocks of code are called methods. Methods are sometimes called functions,
subroutines, or procedures in other programming languages.
The concept of a method is to take a piece of your code and place it inside of a special block and give it a
name, allowing you to run it from anywhere else in your program. We can re-run (“invoke” or “call”) a
method as many times as we want, which means we don’t have to retype it, saving ourselves time.
In this chapter, we’ll look at how to create and use methods, as well as how to get data to and from a
method. We’ll also look at overloading methods, which is giving two or more methods the same name.
We’ll then revisit the Console and Convert classes from earlier chapters, armed with the new knowledge
we have about methods. We’ll wrap up our discussion about methods with a quick look at a powerful (but
sometimes confusing) trick that you can use with methods called recursion.
Creating a Method
87
Creating a Method
Let’s get started by creating our first method. If you start with a brand new project, you’ll start with code
that looks like this:
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace Methods
{
public class Program
{
static void Main(string[] args)
{
}
}
}
If you remember from the Hello World program we made back in Chapter 3, this template code already
defines a method (the Main method) for us. At the time, we only had a basic idea of what a method was,
but we know more now. You can see from the template code that the Main method is contained inside of
a class called Program. (We’re still getting to classes.) When it comes down to it, we’ve already been using
a method, without even really thinking about it.
All methods belong to a type. A class is one specific example of a type. Most of the types we create will be
classes, but methods can belong to the handful of other types that we will look at over the course of this
book. Because the method belongs to the class, it is a member of that class.
When we create a method, we need to place it inside of a type. They can’t just be floating around on their
own. For now, we’ve already got the Program class where our Main method is, which is a perfect spot for
our new methods.
So let’s go ahead and make our first method. Let’s make a method that simply prints out the numbers 1
through 10. This new method looks like the following:
static void CountToTen()
{
for(int index = 1; index <= 10; index++)
Console.WriteLine(index);
}
Placing this in the rest of our program might look like this:
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace Methods
{
public class Program
{
static void Main(string[] args)
{
}
static void CountToTen()
{
88
Chapter 15
Methods
for (int index = 1; index <= 10; index++)
Console.WriteLine(index);
}
}
}
This code won’t do anything yet. Remember, our program will run everything in the Main method by
default, and we have nothing in the Main method for the time being. Before we’re done, we’ll need to
have our Main method “call” our newly created CountToTen() method, but we’ll do that in a second. First,
let’s discuss the piece of code that we’ve just added.
There are just a few pieces in play here. The first piece is the static keyword. We’ll talk about this a little
more when we start creating our own classes. (See Chapter 18.) For now, we’ll assume that it’s just
needed for what we’re doing.
The second thing we added is the keyword void. We’ll talk more about this in a second, when we discuss
returning stuff from a method, but basically, the void keyword tells us our method doesn’t produce any
results.
The next part is the method’s name: CountToTen. Like with variables, you can name your method all sorts
of things. Giving methods descriptive names will make it easier to explain what it does. While it is not
required, the standard convention in C# is to have method names start with an upper case letter, while
variables usually start with a lower case letter.
Next we have the parentheses. Right now, there’s nothing inside of our parentheses, but we’ll soon see
that we can put stuff in here, allowing us to hand stuff off to a method.
Lastly, we have a set of curly braces where we put the body or implementation of the method. This is
where we put the code that the method executes when called. Up until now, we have been putting all of
our code in the body of the Main method. As of this chapter, we’ll start putting our code in the body of
different methods to organize our code into separate, reusable tasks.
We can see that in the body of our method, we have a simple for loop. Any of the code that we’ve written
before can go inside of a method, including loops, if statements, math, and variable declarations.
The order that methods appear within a class doesn’t matter. In the example above, we could have put
CountToTen above Main and it wouldn’t have made a difference. The C# compiler is smart enough to
scan through your file and find all of the methods that exist in it.
Calling a Method
Now that we’ve got our method created, we want to be able to use it. Doing this is really easy. Inside of
your Main method, you do the following:
CountToTen();
So your complete code would look like this:
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
namespace Methods
{
public class Program
{
static void Main(string[] args)
{
Returning Stuff from a Method
89
CountToTen();
}
static void CountToTen()
{
for (int index = 1; index <= 10; index++)
Console.WriteLine(index);
}
}
}
Now you can run your program (which automatically starts in the Main method) and see that it jumps
over to the CountToTen method and does everything inside of it. When it reaches the end of a method, it
goes back to where it came from. So looking at this code:
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
namespace Methods
{
public class Program
{
static void Main(string[] args)
{
Console.WriteLine("I'm about to go into a method.");
DoSomethingAwesome();
Console.WriteLine("I'm back from the method.");
}
static void DoSomethingAwesome()
{
Console.WriteLine("I'm inside of a method! Awesome!");
}
}
}
This will result in the following output:
I'm about to go into a method.
I'm inside of a method! Awesome!
I'm back from the method.
It is also worth mentioning that you can call a method from inside of any other method, so you can go
crazy creating and calling methods all over in your program. (In fact, that’s essentially what software
engineering is! Creating and calling methods in a way that minimizes the craziness.)
Returning Stuff from a Method
Methods are created to do some specific task. Often, that task involves figuring something out, and we
want to be able to know the results. A method has the option of giving something back. (How generous of
them!) This is called returning something. (Even if it doesn’t return anything, like the first method we wrote
a second ago, people still talk about “returning from a method.”)
So far, all of the methods we’ve looked at haven’t returned anything. We’ve always been using the void
keyword. This means that the method doesn’t return anything. By changing that, our method can return
something. To do this, instead of the void keyword, we simply place a different type there, like int, float,
or string. This makes it so our method can (and must) return a value of that particular type.
90
Chapter 15
Methods
Then inside of the method, we use the return keyword to return a specific value. This is followed by the
value we want to return. A very simple example of returning a value is this:
static float GetRandomNumber()
{
return 4.385f; // Obviously very random.
}
You can see that we start off by stating the return type (float in this case, meaning we’re going to return
something of the type float), and inside of the method, we simply use the return keyword, along with the
actual value we want to return. Obviously, this is not a very useful example, but it illustrates the point.
Let’s do something more practical. In this book, there have been many times that we’ve done things like
ask the user to type in a number. The code below creates a method that asks the user for a number
between 1 and 10, and keeps asking until they finally give us something that works. At that point, we’ll
return the number the user gave us.
static int GetNumberFromUser()
{
int usersNumber = 0;
while(usersNumber < 1 || usersNumber > 10)
{
Console.Write("Enter a number between 1 and 10: ");
string usersResponse = Console.ReadLine();
usersNumber = Convert.ToInt32(usersResponse);
}
return usersNumber;
}
If your method returns nothing (void), you don’t need a return statement. But if it does return something,
we’re required to have a return statement.
If a method returns something, we can grab the value returned by the method and do something with it,
like put it in a variable or do some math with it. For instance, the code below will take the value that is
returned by the GetNumberFromUser method and store it in a variable:
int usersNumber = GetNumberFromUser();
As it turns out, we’ve been doing stuff like that all along! But now we can better understand what’s going
on. For instance, when we’ve written things like string usersResponse = Console.ReadLine();, we’re just
calling a method (ReadLine) that belongs to the Console class and getting the value returned by it!
While the return statement is often the last line in a method, it doesn’t have to be. For instance, you can
have an if statement in the middle of a method that returns “early” before the end of the method:
static int CalculatePlayerScore()
{
int livesLeft = 3;
int underlingsDestroyed = 17;
int minionsDestroyed = 4;
int bossesDestroyed = 1;
// If the player is out of lives, they lose all of their points.
if(livesLeft == 0)
return 0;
// Otherwise, the player gets 10 points per underling destroyed, 100 points
// per minion, and 1000 points per boss.
return underlingsDestroyed*10 + minionsDestroyed*100 + bossesDestroyed*1000;
}
Passing Stuff to a Method
91
If your method’s return type is void, you don’t need the return keyword anywhere, but it can still be used
alone to return early:
static void DoSomething()
{
int aNumber = 1;
if(aNumber == 2)
return;
Console.WriteLine("This only gets printed if the 'return' statement wasn't executed.");
}
As soon as a return statement is hit, the flow of execution immediately goes back to where the method
was called from—nothing more gets executed in the method.
You can’t make the return type of a method be var. You must always explicitly state the type.
Passing Stuff to a Method
Sometimes, we want a method to do stuff with certain pieces of information. We can hand stuff off to a
method by putting what we want it to work with inside of the parentheses. Earlier, we made a method
that was called CountToTen, which simply printed out the numbers 1 through 10. We can create an
alternate method that requires you to supply a number to count to. The method could then be used to
count to 10, 25, 1000, or anything else.
To do this, we need a way to hand off information for the method to use. This is done by putting a list of
variables inside of the parentheses when we’re defining the method. Our modified version of the
CountToTen method, which works for any number, might look like this:
static void Count(int numberToCountTo)
{
for(int current = 1; current <= numberToCountTo; current++)
Console.WriteLine(current);
}
Where you define the method, you identify the variable’s type and give it a name to use throughout the
method. This particular kind of variable is called a parameter. The variable numberToCountTo is a
parameter.
When you call a method that has a parameter, you “pass in” or hand off a value to the method by putting
it in the parentheses:
Count(5);
Count(15);
This code, in conjunction with the example before it, will first print out the numbers 1 through 5, and then
it will print out the numbers 1 through 15.
Like with the return value, a parameter must have an explicit type. You cannot use var.
Passing in Multiple Parameters
You can also create a method that passes in multiple parameters. All of the parameters listed—everything
inside of the parentheses—is called the parameter list. The code below is a simple example of passing two
numbers into a Multiply method that multiplies them together, and returns the result:
static int Multiply(int a, int b)
{
92
Chapter 15
Methods
return a * b;
}
Having multiple parameters like this is extremely common. There’s a technical limit to how many
parameters you can have (just over 65,500) but the practical limit is far lower. Most programmers will start
complaining about the number of parameters a method has well before you even reach 10. If you need
that many parameters, you should spend some time trying to come up with an approach that lets you
break the task down into smaller tasks, where each task requires fewer pieces of data to work on.
Try It Out!
Reversing an Array. Let’s make a program that uses methods to accomplish a task. Let’s take an
array and reverse the contents of it. For example, if you have 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, it would
become 10, 9, 8, 7, 6, 5, 4, 3, 2, 1.
To accomplish this, you’ll create three methods: one to create the array, one to reverse the array, and
one to print the array at the end.
Your Main method will look something like this:
static void Main(string[] args)
{
int[] numbers = GenerateNumbers();
Reverse(numbers);
PrintNumbers(numbers);
}
The GenerateNumbers method should return an array of 10 numbers. (For bonus points, change
the method to allow the desired length to be passed in, instead of just always being 10.)
The PrintNumbers method should use a for or foreach loop to print out each item in the array.
The Reverse method will be the hardest. Give it a try and see what you can make happen. If you get
stuck, here’s a couple of hints:
Hint #1: To swap two values, you will need to place the value of one variable in a temporary location
to make the swap:
// Swapping a and b.
int a = 3;
int b = 5;
int temp = a;
a = b;
b = temp;
Hint #2: Getting the right indices to swap can be a challenge. Use a for loop, starting at 0 and going
up to the length of the array / 2. The number you use in the for loop will be the index of the first
number to swap, and the other one will be the length of the array minus the index minus 1. This is to
account for the fact that the array is 0-based. So basically, you’ll be swapping array[index] with
array[arrayLength – index – 1].
Method Overloading
One cool, but potentially confusing thing that you can do with methods is create multiple methods with
the same name. This is called method overloading or simply overloading.
Method Overloading
93
While two methods can have the same name, they can’t have the same signature. A method’s signature is
defined as the combination of the method name and the types and order of the parameters that get
passed in. Note that this does not include the parameters’ names, just their types.
To help illustrate what a method signature is, take the following example:
static int Multiply(int a, int b)
{
return a * b;
}
This has a signature that looks like this: Multiply(int, int).
You can only overload a method if you use a different signature. So for example, the following works:
static int Multiply(int a, int b)
{
return a * b;
}
static int Multiply(int a, int b, int c)
{
return a * b * c;
}
This works because the two multiply methods have a different number of parameters, and as a result, a
different signature. We could also define a Multiply method that has no parameters (int Multiply()), or
one parameter (int Multiply(int)), though in this particular case, I can’t imagine how either of those two
methods would do multiplication with zero or one thing. (Hey, it’s just an example!) Likewise, you could
define a Multiply method with eight or ten parameters, if there was a need for it.
Also, you can have the same number of parameters, if their types are different:
static int Multiply(int a, int b)
{
return a * b;
}
static double Multiply(double a, double b)
{
return a * b;
}
This works because the two Multiply methods each have their own signature (Multiply(int, int) and
Multiply(double, double)).
The following does not work:
static int Multiply(int a, int b)
{
return a * b;
}
static int Multiply(int c, int d) // This won't work. It has the same signature.
{
return c * d;
}
The magic of method overloading is that you can have many methods that do very similar work on
different types of data (like the int multiplication and the double multiplication above) without needing a
completely different method name. With different signatures, the C# compiler can easily determine which
of the overloaded methods to use.
94
Chapter 15
Methods
You should only overload methods when you are trying to do the exact same kind of thing with different
kinds of data. If two methods have the same name, they should perform essentially the same task. If you
know what one does, it should be obvious what the other does, even if it uses slightly different data. If
that’s not the case, you should use a different method name altogether to avoid confusion.
Revisiting the Convert and Console Classes
With a basic understanding of how methods work in C#, it is worth a second discussion about some of the
classes that we’ve already been using. For instance, in the Console class, we’ve done things like this:
Console.WriteLine("Hello World!");
With our new knowledge of methods, we can see that there is a Console class, which has a method
named WriteLine that we call. The WriteLine method has one parameter, which is a string. We pass in
the string “Hello World!” and the method runs off and does its job.
This is a perfect example of why we use methods. Because someone already made a WriteLine method
that writes stuff to the console window, we don’t need to write it ourselves, nor do we need to worry
about how it actually goes about its job. All we care about is that it does it, and that it is easy to use. This
frees us up to worry about the more unique or interesting parts of our programs.
We see similar things with the Convert class, which we have used several times as well:
int number = Convert.ToInt32("42");
The Convert class has a ton of methods that let you convert many types to other types. In the example
above, we see a method called ToInt32, which takes a string as a parameter. This class uses method
overloading extensively, because it has a ToInt32 method that takes a string, one that takes a double,
one that takes a short, and so on.
XML Documentation Comments
In Chapter 4, we introduced the idea of comments. I mentioned that there is one additional way to do
comments that we couldn’t really talk about back then. We’re ready to discuss it here.
It is a good idea to add a comment by a method to describe what it does. This way, when you or
somebody else wants to use it, they can easily figure out what it does, and how to make it work. This is
especially important because methods are designed to be reused.
You can add comments to a method using any of the methods we discussed in Chapter 4, but there is one
additional way that works really well if you are trying to describe what a method does. (By the way, this
also applies to classes, structs, enumerations, and other things that we’ll discuss shortly.) This additional
way is called XML documentation comments.
Let’s say you have a Multiply method like we were talking about a second ago:
public static int Multiply(int a, int b)
{
return a * b;
}
To add XML documentation comments, go just above the method, and type three forward slashes: ///
As soon as you hit that third forward slash, Visual Studio will pop in a big comment that looks like this:
/// <summary>
///
/// </summary>
The Minimum You Need to Know About Recursion
95
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
This is your XML documentation comment. In between the <summary> and </summary> lines, you can
add a description of what the method does. In between the param tags, you can describe what each
parameter of your method does, and in the returns tags, you can add what the return value should be. A
completely filled in example of this might look like this:
///
///
///
///
///
///
<summary>
Takes two numbers and multiplies them together, returning the result.
</summary>
<param name="a">The first number to multiply</param>
<param name="b">The second number to multiply</param>
<returns>The product of the two input numbers</returns>
The nice thing about adding in an XML documentation comment is that Visual Studio’s IntelliSense will
immediately start showing it as we work on our code (Chapter 45).
The Minimum You Need to Know About Recursion
There’s one additional trick people use with methods that I think is worth bringing up here. It’s kind of a
complicated trick, so right now you don’t need to master it. But it is worth mentioning, so you know what
it is when it comes up, and so you can start thinking about how it might be useful.
The trick is called recursion, and it is where you call a method from inside itself. Here’s a trivial example
(which happens to break when you run it):
static void MethodThatUsesRecursion()
{
MethodThatUsesRecursion();
}
This example is going to break on you because it just keeps going into deeper and deeper method calls.
Eventually, it will run out of memory to make more method calls, and the program will crash.
The problem with this first example, is that there is no base case. Recursion always needs a state where it
doesn’t call itself again, and each time it calls itself, it should be getting closer and closer to that base case.
If not, it will never get to a point where it is done, and can start returning from the method calls back up to
the starting point.
One of the classic examples of when you could use recursion is the mathematical factorial function.
Perhaps you remember from math classes that factorial, written as an exclamation mark by a number,
means to take it and multiply it by all smaller numbers. For example, 7! means 7 * 6 * 5 * 4 * 3 * 2 * 1.
72! would be 72 * 71 * 70 * 69 * ... * 3 * 2 * 1. (With factorial, by the way, you really quickly run into the
overflow issues that we were talking about back in Chapter 9.)
In this case, we know that 1! is 1. This can serve as a base case. The factorial of any other number can be
thought of as the number multiplied by the factorial of the number smaller than it. 7! is the same as 7 *
6!.
This sets up a great opportunity for recursion:
static int Factorial(int number)
{
// We establish our "base case" here. When we get to this point, we're done.
if(number == 1)
return 1;
96
Chapter 15
Methods
return number * Factorial(number - 1);
}
Try It Out!
The Fibonacci Sequence. If you’re up for a good challenge involving recursion, try out this challenge.
The Fibonacci sequence is a sequence of numbers where the first two numbers are 1 and 1, and
every other number in the sequence after it is the sum of the two numbers before it. So the third
number is 1 + 1, which is 2. The fourth number is the 2nd number plus the 3rd, which is 1 + 2. So the
fourth number is 3. The 5th number is the 3rd number plus the 4th number: 2 + 3 = 5. This keeps
going forever.
The first few numbers of the Fibonacci sequence are: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ...
Because one number is defined by the numbers before it, this sets up a perfect opportunity for using
recursion.
Your mission, should you choose to accept it, is to create a method called Fibonacci, which takes in a
number and returns that number of the Fibonacci sequence. So if someone calls Fibonacci(3), it
would return the 3rd number in the Fibonacci sequence, which is 2. If someone calls Fibonacci(8), it
would return 21.
In your Main method, write code to loop through the first 10 numbers of the Fibonacci sequence and
print them out.
Hint #1: Start with your base case. We know that if it is the 1st or 2nd number, the value will be 1.
Hint #2: For every other item, how is it defined in terms of the numbers before it? Can you come up
with an equation or formula that calls the Fibonacci method again?
16
16 Value and Reference
Types
In a Nutshell






Your program’s memory is divided into several sections including the stack and the heap.
The stack is used to keep track of your program’s state and local variables.
The heap is used to store data that is accessible anytime from anywhere.
The CLR manages memory in the heap for you. It cleans up dead memory using garbage
collection.
Value types store their data directly inside of the variable. Reference types store a reference
to a location in the heap, where the rest of the data is stored.
Value types have value semantics, which means that when you assign one variable the
contents of another, the entire contents of the variable are copied, so you get a complete
copy in the other variable. Reference types have reference semantics, which means when you
assign one variable to another, only the reference is copied, and the two are referencing the
same object. Changes to one affects the other.
We’re going to change gears a little and spend a chapter in pure learning mode, instead of coding mode.
We’re going to discuss a few topics that are going to be important to understanding types and how classes
work. This chapter is probably the most technical chapter in the book, but trust me when I say it is both
important and useful.
The Stack and the Heap
When your program first starts, the operating system gives it a pile of memory for it to work with. Your
program splits that up into several areas that it uses for different things, but most of it is used by two
sections for storing data as your program runs. These two sections are the stack and the heap. The stack
and the heap are used for slightly different things, and they’re organized in different ways.
98
Chapter 16
Value and Reference Types
The stack is used to keep track of the current state of
the program, including all of the local variables that
you have. The stack behaves like a stack of
containers. Whenever we enter a method, we place a
new container on the top of the stack. These
containers on the stack are called frames. All of the
local variables (including parameters) for a particular
method go in a single frame.
Additionally, a frame will include information to keep
track of the program’s current state of execution, like
what line of code the program was at just before
entering the new method. In fact, one of the primary
purposes of the stack is to keep track of the
program’s current state of execution.
Like with a stack of containers, you can readily access
the contents of the frame on the top of the stack, but
you can’t really get to the containers lower down.
When we return from a method, the top frame is
taken off the stack and discarded, and we go back
down to the frame beneath it.
Interestingly, the debugger that is built into Visual Studio can inspect the entire stack, including the buried
frames, and show them to you. (This is called a stack trace.)
The heap, on the other hand, is not
concerned at all about keeping track of
the program’s state. Instead, it is only
focused on storing data. The heap is
organized in a way where it is easy to get
access to things any time you need. It’s
easy for the program to grab some space
in the heap and start using it to store the
information it needs. There’s not
necessarily any logical organization to
what gets stored where on the heap, so it
is easy to think of it as just a pile of individual blocks of data.
If you have a multi-threaded application (Chapter 39) each thread will have its own stack, but all threads in
the program will share the same heap.
Memory Management and Garbage Collection
A program needs to manage the memory it is using. In particular, it needs to clean up old memory that is
no longer being used. For the stack, this is easy. Any time it returns from a method, it knows it can throw
the top frame away and reuse that space immediately.
The heap is a little more complicated. Back in the day, when you put stuff in the heap, you needed to
remember it, and when you were done using it you had to tell the computer you were done with it. This
would allow you to reuse that space again.
References
99
If you were putting something on the heap for just a moment, and then getting rid of it right after, this
was easy to do. But this was not so simple when what you were putting on the heap needed to stay there
a while before being cleaned up. It was very easy to forget that it was even there, and even if you
remembered, it was usually all too easy to fail to free that memory correctly. You’d end up with garbage in
the heap that you no longer wanted, but the computer didn’t know it could reuse. This is called a memory
leak, and if you had one (or many) then your program was eventually going to run out of space and die.
Fortunately for us, C# uses an approach where the .NET Platform manages your heap’s memory for you. If
you’re starting to run low on memory for your program, a part of the .NET Platform called the garbage
collector will run through and get rid of any old, unused stuff on your heap, freeing it up to be reused. If
you’ve never had to manage your own program’s memory, it may be hard to see this, but garbage
collection is a huge deal, and it saves you lots of time, worry, and customer complaints.
References
Since data on the heap is sort of scattered about rather than structured like the stack is, you will need a
way to keep track of the variable or data you are interested in.
Outside of C#’s automatic memory management, you would access things in the heap using a pointer,
which is special variable type that stores a memory address in the heap, rather than the actual data. You
can then use this memory address to hunt down the data you want from the heap as needed.
With C#, the heap’s memory is managed for you. This “managed”-ness primarily means two things to you.
First, when objects on the heap are no longer in use (nothing in your program can reach it anymore) the
runtime’s garbage collector will automatically free up the memory for you. Second, the garbage collector
will move things around from one memory location to another to keep things organized and ensure that
there are always large, contiguous blocks of memory available for the next memory allocation.
Because the garbage collector can move stuff around on you, pointers containing a raw memory address
won’t work for you. The data may get moved to a different memory location without you even knowing.
Instead, C# uses a thing called a reference, which is conceptually similar to a pointer, but the reference to
the data is also maintained for you by the garbage collector. Even when data gets moved around in
memory, the reference will still be able to get you to the data you want.
It is helpful to think of a reference as an arrow pointing you to a specific item or variable within the heap.
But technically speaking, references are more of a unique identifier that tells the computer where to find
the rest of the data within the heap.
Value Types and Reference Types
In C#, all types are divided into two broad categories: value types and reference types. With our
discussion on the stack vs. the heap, and what we now know about references, we’re ready to dig into the
difference between these two. This is one of the biggest stumbling blocks or misunderstandings for both
new programmers and experienced programmers, so take the time to make sure you understand how
these two different categories of types work.
Value and reference types are one of the biggest things that make C# stand out from C++, Java, and other
programming languages, which do things in a completely different way.
Here is the key difference: with a value type, the actual contents of the variable live where the variable
lives. With a reference type, the contents of the variable live on the heap, and the variable itself contains
only a reference to the actual content.
100
Chapter 16
Value and Reference Types
Go back and read that last paragraph again a time or two to make sure you understand it.
It turns out we have already been using both value and reference types. The string type is a reference
type, and arrays are references as well (though the contents of the array can be either reference or value
types). All of the simple types that we’ve discussed before, as well as enumerations (Chapter 14) are all
value types.
As we get started with Part 3 of this book, we’re going to learn how you can create your own custom value
or reference types, and finish filling in our type system diagram.
Let’s pick this apart a little more and get into some details and examples. We’ll start with reference types.
Look at the following code:
string message = "Hello!";
Since the string type is a reference type, the actual “Hello!” text will always be stored in the heap, and the
message variable will simply contain a reference to that text. As a local variable or parameter, the
message variable will be stored on the stack somewhere, and contain a reference to that actual text on
the heap.
Depending on how things are set up, reference type variables can also live on the heap, so you could have
references from one part of the heap pointing to other parts of the heap. This would be the case if you
have an array of strings, both of which are reference types. For instance, look at the following:
string[] messages = new string[3] { "Hello", "World", "!" };
This might look like this:
Null: References to Nothing
101
A local messages variable on the stack has a reference to the string array, but that actual array lives on
the heap. Within that array, each item contains another reference to other parts of the heap—the strings
that go in the array in this case. Taking this further, it is entirely possible to have huge networks of
references spread throughout the heap.
On the other hand, value types are stored entirely at the place that the variable exists. If they’re local
variables or parameters, this means they’re stored entirely on the stack. We’ve seen this with nearly all of
the built-in types that we’ve been working with so far, like int and double. The variable doesn’t contain a
reference, just the actual value.
This still holds true, even when the value type is defined on the heap. Value typed variables will always
contain their data right there where the variable is at. Arrays give us a great example of how this works.
Let’s say you had an array of value types, like this: int[] numbers = new int[3] { 2, 3, 4 };. This would end
up looking something like this:
Note that the items in the array don’t point to elsewhere like they did with our string array earlier. The
numbers exist right where the variable exists.
That covers the basics of how value and reference types work. In a minute, we’ll come back and take a
look at what that means for us, when we talk about value and reference semantics. But first, a little bit of
a detour to cover something else that we need to know about reference types.
Null: References to Nothing
One interesting thing about reference types is that you can have them reference nothing at all. This is
called a null reference. Assigning a reference type a value of null can be done with the null keyword:
string message = null;
int[] numbers = new int[] { 2, 4, 6, 8 };
numbers = null;
With the middle line, we create a new array, which is placed on the heap, and then we immediately
reassign numbers to null. At this point, the numbers variable isn’t referencing that array anymore, and
since nothing else is referencing it either, it has become inaccessible, and isolated on the heap. Because it
is inaccessible, eventually it will be garbage collected.
If a reference type has a value of null, it means there’s no data at the other end. If you inadvertently try to
actually do something with it, your program is going to crash:
int[] numbers = null;
numbers[3] = 6;
// This crashes, since numbers doesn't reference anything.
To address this, you can do a simple check to see if a reference type is null first:
102
Chapter 16
Value and Reference Types
if(numbers != null)
numbers[3] = 6;
Value types cannot be assigned a value of null.
Checking for null is incredibly common. While the approach above covers you in all cases, C# 6 introduced
a new pair of operators that allow you to check for null using much more concise syntax. For more
information on this other syntax, see the section called Simple Null Checks: Null Propagation Operators in
Chapter 43.
Value and Reference Semantics
If the only difference between value and reference types was whether they stored their data “on site” or
off elsewhere in the heap, I wouldn’t normally worry about that kind of detail for an introductory book like
this. However, there’s more to it. Because of this difference, we get different behavior.
Reference types have what’s called reference semantics, and value types have value semantics or copy
semantics. Let’s compare these two with an example, using the int type (a value type) and an array of ints
(the array is a reference type, even though it contains things that are value types). Let’s say you have the
following code:
int a = 3;
int b = a;
b++;
We’ve created two variables with the int type. We place the value 3 inside a. We then assign the contents
of a to b. Doing this means reading the contents of a (which is 3) and putting that value into b. Next, we
modify b by incrementing it so b is now 4. But a is still the original value of 3. When we assign the
contents of a to b, we made a new copy for b. Whatever happens with the b variable won’t affect the
original a variable.
Interestingly, the exact same thing happens with reference types, but because the variable contains only a
reference instead of the actual value, we get different behavior entirely. Take a look at this example:
int[] a = new int[] { 1, 2, 3 };
int[] b = a;
b[0] = 17;
Console.WriteLine(a[0]); // This will print out 17.
When we create our array, the entire contents of the array are placed in the heap. While a may be stored
on the stack, it will contain a reference to the array out in the heap. When we assign the value of a to the
variable b, we read the value from a, which is a reference, and
copy it into b. But copying a reference gives b a reference to the
same object on the heap. At this point, both of our variables
are referencing the same array in the heap. We then modify
that array by going through the b variable, but since the two
things reference the same memory location in the heap, a has
been modified as well.
This same thing happens as you send things to methods as parameters. For value types, the value is
copied over to the parameter. Inside the method, we have a copy of the data. Changing the variable there
won’t affect the original, back in the calling method.
With reference types, while you’re still technically passing a copy of the variable’s contents, it is a copy of a
reference, so inside of the method, the parameter is referencing the exact same thing on the heap.
Making changes to it inside of the method affects the thing back in the calling method.
Value and Reference Semantics
103
This is often done intentionally, handing an array or other reference type to a method with the specific
task of modifying it in some way. But of course, if you hand a reference type to a method, expecting it to
remain unchanged, and the method makes changes, you’ll be in for a surprise.
This can be a useful thing or a liability, but it is a fact. You have to understand the differences between
value types and reference types, or it will come back to haunt you repeatedly. Surprisingly, there are a lot
of talented programmers who claim to know C# that haven’t figured this heap vs. stack and value vs.
reference type stuff yet. If you don’t understand these differences, you will be constantly running into
strange problems that just don’t seem to make sense.
Try It Out!
Value and Reference Types Quiz. Answer the following questions to check your understanding.
When you’re done, check your answers against the ones below. If you missed something, go back and
review the section that talks about it.
1.
2.
3.
4.
5.
6.
7.
8.
9.
True/False. Anything on the stack can be accessed at any time.
True/False. The stack keeps track of local variables.
True/False. The contents of a value type can be placed in the heap.
True/False. The contents of a value type are always placed in the heap.
True/False. The contents of reference types are always placed in the heap.
True/False. The garbage collector cleans up old, unused space on both the heap and stack.
True/False. You can assign null to value types.
True/False. If a and b are reference types, and both reference the same object, modifying a
will affect b as well.
True/False. If a and b are value types with the same value, modifying a will affect b as well.
Answers: (1) False. (2) True. (3) True. (4) False. (5) True. (6) False. (7) False. (8) True. (9) False.
Part
3
Object-Oriented
Programming
C# is an object-oriented programming language, meaning that the code we write is typically organized
into little blocks that are modeled after real-world objects. We define what kind of data those objects
have and what those objects can do.
The object-oriented aspect of C# is a key part of the language. Without knowing how to create and
use classes, our understanding of the language is far from complete.
Part 3 is all about designing and building your own types in C#. Building your own reusable types and
assembling them together is how you’ll be able to make truly amazing software.
We’ll dig straight to the heart of object-oriented programming. We’ll look at the following:







Introduce what object-oriented programming is about (Chapter 17).
Discuss how to make your own classes (Chapter 18) and structs (Chapter 21).
Create easy access to data in your own custom types with properties (Chapter 19).
A single chapter devoted to a large Try It Out problem: Tic-Tac-Toe (Chapter 20).
Make classes that are special types of other classes with inheritance (Chapter 22).
Making special types that can do the same tasks in their own way (Chapters 23 and 24).
Make generic types (Chapters 25 and 26).
17
17 Object-Oriented Basics
In a Nutshell





Object-oriented programming is a programming style where you create chunks of code that
are modeled after real world objects.
Classes in C# define what an entire “class” of objects can do (what all players can do, or what
any car can do), including what kind of data it stores and the tasks that it can do.
Classes are reference types.
Creating a class is the most powerful way C# has of defining your own new types.
You can create a new object with something like this: Random random = new Random();.
This creates a new Random object, which is used to generate random numbers.
We’ve worked with a lot of different types so far: int, string, arrays, etc. We even learned how to define
our own simple enumeration types. The next step in our journey is to begin to work with and make our
own custom types from scratch. Custom types are defined by assembling data that defines the
information that the type has, as well as methods that operate on that data. These custom-made types
form the fundamental building block of “object-oriented programming.” This chapter will primarily focus
on concepts of this important programming style, and get us some practice with using classes and
objects. We’ll begin making our own class types in the next chapter.
Object Classes and Object Instances
Over the many years that people have been programming, we’ve come to learn that one of the best ways
to structure code is to mimic the way the real world works. It helps to break things down into manageable
pieces, and presents an intuitive way for programmers to work with their code. This concept of making
our code mimic real-world objects is called object-oriented programming.
Read that last paragraph again. It’s important.
In object-oriented programming, there are two ways of thinking about the objects that exist. The first is to
think in terms of categories or types of objects. For example, in the real world, we have houses, and cars,
and planets. These are all general categories of objects. Or we could say that these are “classes” of
objects.
108
Chapter 17
Object-Oriented Basics
Additionally, there are specific occurrences or “instances” of these object classes. This house, that house,
the White House, and that weird house that is painted pink on the next block over.
Each category or class of objects have certain properties in common, or share certain functionality. For
example, when talking about a house, we know all houses have a size or a color. Each specific house will
have its own unique size measurement or color, but we know all houses will have a size measurement
and a color of some sort.
Here is another example that might be more applicable in a game development scenario. An Asteroids
clone needs to deal with asteroids. We know all asteroids in the game will have a position in the game
world and a size. Each individual asteroid will have its own unique position and its own unique size. We
can talk about asteroids in the general sense (the entire class of asteroid-type things) and we can also talk
about specific asteroids. One over here is at (60, 121) and has a size of 15, and another one over here is at
(542, 97) and has a size of 5.
With object-oriented programming, we can define a blueprint for a specific type or category of object by
defining a class. This is a new, custom type that we make, similar to when we made our own enumeration
types.
These class definitions allow us to specify what data a particular class of object has in common. If we were
defining an Asteroid class for example, we would probably want to indicate that asteroids have a position
and a size.
In addition to data, a class definition will also allow us to define behavior that all things that belong to the
class can perform. Continuing with the Asteroids game theme, if we had a Ship class that represented
what the player controlled, this might include the ability to Fire a bullet, or Warp to a different location.
These behaviors are defined as methods (Chapter 15), and they can use and also modify the data that
belongs to the class.
Based on a class definition, we can then create specific instances of the class within our programs. For
example, our Asteroids clone could create a dozen different instances of the Asteroid class and different
locations and with different sizes. And it could then create one or two Ship instances for a one or two
player game. Each instance has its own position. Each ship can warp or fire separately from the others,
but because all ships “belong to” the same Ship class or category, we know they all have the capability to
fire or warp. Different instances of a class are independent of each other. They have their own set of data
and asking them to perform an action or method is for a specific instance, rather than for the whole class
of objects. But if two instances are of the same class, we know we can interact with them in the same way
because they share the same blueprint.
People frequently use the term instance and object interchangeably to mean a specific occurrence, with its
own unique data.
These two concepts—the idea that classes of objects share the same blueprint, and that unique instances
of a certain type are independent of other instances of the same type but can be interacted with in the
same way—form the foundation of object-oriented programming.
Working with an Existing Class
We will soon turn our attention to defining our own classes, but before we do, let’s start by using a simple
class that already exists: the Random class. The Random class lets you create random numbers. For
example, you could use this class to simulate rolling dice, or to shuffle a deck.
Using an Instance
109
In Depth
Computers and Randomness. Generally speaking, random numbers on a computer aren’t truly
random. They are actually chosen using an algorithm that simply appears random. They need to be
initialized, starting with a number chosen by the programmer. This number is called a seed. If you
start with the same seed, you will get the same sequence of random numbers, over and over again.
Since the vast majority of the time, this is not something you want, programmers will often seed the
random number generator with the current time, accurate to the millisecond. This makes it so that a
different sequence is generated every time you run the program.
The Random class that we’re using here will automatically seed the random number generation with
the current time, but you can also specify a seed if you would like.
We can use the Random class in a way that is very similar to any other type:
Random random = new Random();
In this case, we specify Random as our variable’s type and give our variable a name (random). Like with
any other variable, we can assign a value by using the assignment operator (=).
But after that, we’ll see a couple of things that we haven’t really seen. You can see the new keyword there.
This tips us off to the fact that we’re going to create a new instance of the Random class—an object with
the Random type.
The Random() part might look like a method call to you, and it kind of is. In fact, it is a special kind of
method called a constructor. A constructor is placed in a class and describes how to create or build a new
instance of that type. This particular constructor has no parameters (hence the empty parentheses) but it
is possible to have constructors with parameters, like any other method.
When that line is finished executing, our variable called random will contain a reference to a brand new
instance with the type Random.
Using an Instance
Now that we have a working object, we can do stuff with it. If you remember, an object has state, and it
has behaviors. In the specific case of the Random object we just created, internally, it is keeping track of
its state, but it doesn’t expose any of that to us.
So while we can’t modify a Random object’s state, we can use its behaviors, defined by its methods. A
different type might expose some of its state and provide methods that allow us to request changes to
that state (like our Ship’s Fire and Warp methods).
We can access our object’s behaviors through the methods it has defined in it. For starters, there’s the
Next method. We call this method by using the dot operator (“.”):
Random random = new Random();
int aRandomNumber = random.Next();
The dot operator is used for member access. In other words, to access something that belongs to
something else. Here, we use it to say, “Look in random to find the method called Next.” Things that
belong to an object, like variables and methods, are called members of the type and are accessible with
the dot operator.
110
Chapter 17
Object-Oriented Basics
The Next method is overloaded (Chapter 15), so there are multiple versions of the Next method,
including one that lets us choose a range to use:
Random random = new Random();
int dieRoll = random.Next(6) + 1; // Add one, because Next(6) gives us 0 to 5.
The Random class also has a NextDouble method, which returns floating point numbers between 0 and
1. This allows you to do things like random probabilities and so on.
Once you have created an object like this, you can use any of its methods in any order, at any time.
Try It Out!
Die Rolling. Tons of games use dice. The Random class gives us the ability to simulate die rolling.
Many games give the player the task of rolling multiple six-sided dice and adding up the results.
We’re going to write a program that makes life easier for the player of a game like this. Start the
program off by asking the player to type in a number of dice to roll. Create a new Random object and
use the Random.Next method to simulate that many die rolls. Add the total up and print the result
to the user. (You should only need one Random object for this.)
For bonus points, keep looping and handle new numbers until they enter “quit” or “exit.”
The Power of Objects
Classes are a core part of object-oriented programming languages like C#. In fact, you could argue they
are the core part. Classes and instances are the fundamental building blocks of programming in these
languages. In C#, all of your code will belong to a particular class (or another custom-made type). In fact,
all of the code we’ve been writing so far has been inside of a Program class! (Starting in the next chapter
though, we’re going to be building our own classes, and putting code elsewhere.)
Classes of objects and specific instances of objects are an idiom borrowed from the real world, which is
something programmers have plenty of experience with. This idiom allows you to build large programs by
breaking the sum total of the functionality down into smaller pieces that each do a very small subset of
the total. By modeling pieces of your program after real-world objects or conceptual components to a
solution, it becomes easier to see how a vast program made up of many small pieces is combined
together in the right way to get the desired behavior.
Classes are Reference Types
Anything that is defined as a class is a reference type. This means that everything we learned in Chapter
16 about reference types applies to classes. For example, you can assign null to it:
Random random = null;
And two variables that are assigned the same reference will affect each other, because they use reference
semantics:
Random random1 = new Random();
Random random2 = random1;
// random1 and random2 are actually referencing the same Random object now.
And like all reference types, this applies even as you pass the contents of a variable to a method as a
parameter. If you pass an instance of a class to a method, the parameter inside of the method will
reference the exact same instances as whatever was passed in. This means changing the object from
Classes are Reference Types
111
inside of the method will affect variables that reference the same instance in the calling method, for
better or worse.
public static void Main(string[] args)
{
Random random = new Random();
DoSomething(random);
}
public static void DoSomething(Random r)
{
// The variable 'r' references the same instance as 'random' in Main. Two references, same instance.
Console.WriteLine(r.Next());
}
18
18 Making Your Own Classes
In a Nutshell








Classes typically go in their own file (though C# doesn’t require it).
Anything that is a part of a class (or other type) is called a member. This includes instance
variables, methods, and constructors.
Variables can be added to a class—these are called instance variables.
You can add as many constructors to a class as you want.
You can add methods to a class, which operate on the instance variables that the class has.
A private method or instance variable is only visible from inside the class.
A public method or instance variable can be seen from anywhere, inside or outside the class.
This chapter also covers scope, name hiding, and the this keyword.
In the previous chapter we learned the basics of classes and instances. We’ll now start to make our own
classes in this chapter.
The ability to create your own classes is very important. As you write your own programs in C#, you will
make many, many classes. That’s because building your own classes allows you to break your whole
program into manageable pieces that you can work with.
While we cover a lot of ground in this chapter, this is the end of the beginning. By the end of this chapter,
you will know enough of the fundamentals to program in C#! While there is more to cover, it is possible to
write almost any program you want after this chapter.
Creating a New Class
We’re now ready to create our very first class. Remember from the last chapter that a class works as a
blueprint for what an entire class or category of things can do and keep track of. Through this chapter,
we’ll create and use a simple Book class, which can be used to represent a book in a program.
While you are allowed to put multiple classes in a single file, it is a good idea to put each new class into its
own file. This helps keep your files to a manageable size, and makes it easier to find things.
Creating a New Class
113
Our first step is to create a new file inside of our project. To do this, in the Solution Explorer, right-click on
your project. The very top level item in the Solution Explorer is your solution, which is simply a collection
of related projects. That’s not the one you want. Your solution probably only has one project right now,
and it likely has the same name as the solution. So in most cases, this means you’ll be clicking on the
second item in your Solution Explorer. After right-clicking, choose Add > Class....
This will bring up the Add New Item dialog, with the Class template already selected. At the bottom, type
in the name of your new class. I’ve called mine Book.cs, so my class will be called Book. Press the Add
button, and your new file will be created.
When your new file is created, you’ll see that Visual Studio generated some template code for you.
Book.cs will contain code that looks something like this:
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace CreatingClasses // Your namespace will likely be different here.
{
class Book // Whatever name you chose will appear here as the class name.
{
}
}
114
Chapter 18
Making Your Own Classes
The class that was generated for us is completely empty. Our task now will be to fill in the class with the
things it needs. Anything that is a part of a class is called a member of the class. As we define a new class,
we will be adding three main types of members to it: the data it has (as variables), the things it can do
(methods), and ways to initialize new instances of the class (constructors). We’ll now go through the
process of adding each of these to our new class.
Instance Variables
The first thing we’ll do is add variables to store the data that this object class will have for each instance.
The variables we place in a class define what type of data any instance of the class will have. The class
serves as the blueprint for all instances of the type. While we define what data instances of the class
should have, each instance will actually get its own copy of the variables and data to work with, rather
than sharing the data across all instances. (Though there’s a way to do that too.)
Because of this, the variables that we add directly to a class are called instance variables.
For our simple Book class, we’re going to keep track of the book’s title, author, number of pages, and the
word count. There is undoubtedly more information we could store about a book (publisher, publication
date, etc.) but these four pieces of data are enough to illustrate the point.
Instance variables are made by placing them directly in the class, not in a method like we’ve done before:
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace CreatingClasses
{
class Book
{
private string title;
private string author;
private int pages;
private int wordCount;
}
}
Each instance variable above starts with the private modifier, which we’ll discuss in a second. The rest is
just like creating any other variable—we state the type of variable, followed by its name. (Note that you
can’t use var for instance variables. You must explicitly specify the type.)
These variables are a little different from what we’ve seen up until now, in terms of how they’re used. All
of our earlier variables have been either local variables or parameters, both of which can only be used
within the scope of a single method. (Parameters are defined as a part of the method’s signature, and are
filled in by the calling method, whereas local variables are not. Instance variables on the other hand belong
to the instance as a whole. These can be used from any method (including constructors) across the entire
class. They aren’t limited to just a single method.
Access Modifiers: private and public
Let’s go back to that private keyword. Everything in a class has an accessibility level. An accessibility level
indicates where it can be accessed from. The private keyword makes it only accessible (both to retrieve
and set new values) from inside the class. Anything outside of the class doesn’t even know it exists.
Constructors
115
By contrast, the public accessibility level means that it can be seen or used from anywhere, including
outside of the class. We could have made these variables public. The problem with doing this is that it
makes it too easy for “outsiders” to modify our class’s data in ways that shouldn’t be allowed. For example,
somebody could intentionally or accidentally modify wordCount to be negative, which is meaningless.
A class should protect the integrity of its own data. This is a concept called encapsulation. In general, a
class should keep its internal data private but then allow for public mechanisms to request the current
state of that data or to request applying new values for that data. This allows our class itself, through
those mechanisms, to act as a gatekeeper, and make sure that only reasonable changes are performed.
By the way, if you don’t put an access modifier, these variables would be private by default; private is the
default accessibility level for all members of a class. Because of this, it wasn’t technically necessary to use
private in this case, but I think it’s a good idea to put it in anyway, to make it obvious.
Constructors
Now that we’ve got instance variables ready, our second step is to indicate how you can create new
instances of our class. This is done by adding in special methods called constructors.
When making constructors, we want to think about what we need to do to get our newly created instance
into a valid starting state. This is usually determined in large part by the instance variables that our class
contains. We frequently want constructors that will allow users of the class to supply initial values for
these instance variables.
For instance, it probably doesn’t make sense to create a book without a title. The same thing probably
holds true for an author as well. So we’ll probably want to make a constructor that allows the user to
specify both of these as parameters. The code below adds a constructor that does this to our Book class:
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace CreatingClasses
{
class Book
{
private string title;
private string author;
private int pages;
private int wordCount;
public Book(string title, string author)
{
this.title = title;
this.author = author;
}
}
}
You can see from the above code that constructors look very much like methods, but with a few subtle
differences. The first key difference here is that a constructor must share the same name as the class,
while a normal method is forbidden from doing this. (This is, in fact, the way that the C# compiler
distinguishes the two.)
The second difference is that constructors do not (and cannot) specify a return type. Not even void. By
contrast, methods are required to specify a return type.
116
Chapter 18
Making Your Own Classes
You can also see that we’ve made our constructor public, instead of private like our instance variables.
By making this public, it allows code outside of the class to be able to create instances of the Book class.
Had we made it private, it could only be reachable from inside the class. Since we nearly always want to
be able to create instances of a class from outside the class, constructors will usually be public, though
this isn’t a requirement by any means.
In all other regards, a constructor is the same as a method. You can put any arbitrary code in there, like
we’ve done with other methods, including things like loops and if statements. The constructor is also
allowed to have parameters, like any other method. The variables defined by the parameters are usable
throughout the method.
By the way, if you don’t put any constructors in your class, the C# compiler will put one in for you. That
constructor is parameterless. This is called the default parameterless constructor. Placing any other
constructor of your own in a class will prevent the default parameterless constructor from being added
automatically. If you wrote the default parameterless constructor by hand, it would look like this:
public Book()
{
}
The body of our constructor probably brings up some additional questions. Specifically, what is that this
thing doing in there? To answer that question, we’re going go through a series of important concepts that
shape how both constructors and normal methods work when placed inside of a class.
Variable Scope
With the introduction of instance variables that live directly inside of a class (contrasted with local
variables and parameters, that all live within a single method) we need to talk about an important concept
called scope or variable scope. The scope of a variable refers to the places in the code that a variable is
accessible.
There are generally three main “levels” of scope that we concern ourselves with:



If something has class scope, it means it is accessible across an entire class.
If something has method scope, it means it is accessible across an entire single method, but not
before the line on which it is declared.
If something has block scope or inner block scope, then it is accessible within just a section of a
method. A for loop or foreach loop, for example, can declare a variable for use in the loop itself.
These variables “go out of scope” at the end of the loop.
While not perfect, as a general rule, you can let the curly braces be your guide.
Here is a simple example of scope:
public void DoSomething()
{
int a = 0;
for(int x = 0; x < 10; x++)
Console.WriteLine(x);
x = -5;
// This won't compile--the variable 'x' is "out of scope".
}
In this method, the variable a has method scope, but the variable x has block scope. The a variable on the
other hand can be used throughout the method, because it has method scope. The variable x can be
used within the loop itself, but not after the loop. It won’t even compile. It has fallen out of scope.
Constructors
117
Interestingly, we can create a second for loop, reusing the same variable name (x) for a different variable,
and the two won’t conflict:
public void DoSomething()
{
for(int x = 0; x < 10; x++)
Console.WriteLine(x);
for(int x = 50; x < 60; x++)
Console.WriteLine(x);
}
Going back to our Book constructor, each of the four instance variables that we’ve defined in the class
have class scope. They are accessible throughout the class, including from within the constructor. The
title and author parameters defined at the top of the Book class, by contrast, have method scope.
Name Hiding
Related to the previous topic on variable scope, in some cases, it is possible for a nested scope to name
something the same as something in the broader parent scope that contains it. Our constructor here is a
perfect example of this. We had an instance variable called author that has class scope, and we created a
parameter by the same name that has method scope.
When this happens, the variable in the smaller scope “hides” the variable in the larger scope. This is a
situation called name hiding. That is, while both the class-level author variable and the method-level
author variable both officially exist, within the context of the constructor, the local author variable hides
the class-level author variable, making it inaccessible through direct means.
What do we do about this?
Well one solution is to just always name local variables and parameters something different than the
class-level instance variable.
If you choose to solve this problem this way, I strongly recommend establishing a naming convention. I’ve
seen people be inconsistent about names like this and it causes a great deal of pain. For example, if you
make bookAuthor and title your class-level variables, but author and theTitle your method-level
variables, you’re going to always be confused about which variable belongs to the class and which are
local to the method.
Many C# programmers will prefix the class-level instance variables with either an “m_” or just a plain “_”.
(In this case, the “m” is for “member”.) In other words, we could make our instance variables m_author,
m_title, etc.
This approach solves our name hiding problem by simply avoiding name hiding systematically. The only
real drawback to this is that placing “m_” or even just “_” does have an impact on readability, especially
when you’re literally reading it out loud. (This is more common than you might think, because of how
often programmers will discuss code in pairs or groups.) “Em underscore title” is a whole lot more to say
than just simply “title”. But in the grand scheme of things, this isn’t so bad.
There is another solution to this problem though. There is a way that we can directly reference things in
the class scope, regardless of what local variables might be hiding it. We’ll discuss this in the next section.
The ‘this’ Keyword
At any point in time, you can get access to the current instance and the things in the class scope with the
this keyword that our constructor illustrated. this is a special reference to the current instance that a
method or constructor is running in. It’s a quick and easy way to reach up into the class scope and work
with instance variables and even methods that belong to the class.
118
Chapter 18
Making Your Own Classes
This allows us to sidestep nearly all name hiding issues, and is what I showed in our constructor:
public Book(string title, string author)
{
this.title = title;
this.author = author;
}
Because there is a local variable called title that name hides the class-level title variable, when we refer
directly to title, it will always refer to the method-level title variable. But if we start with “this.”, then we
can reach back up to the class-level directly, and reference the title instance variable.
This is a clean and simple way to avoid name hiding problems while still giving all variables simple, easy to
understand names.
Multiple Constructors
A class can have as many constructors as you want to place in it.
Keep in mind that the goal of a constructor is to ensure that new instances of the class are put into a valid
starting state. This state can be modified later through method calls, but the constructor should ensure
that it always starts in a “safe” state.
As an example, it might make sense with our Book class to provide a second constructor that allows the
user to additionally specify the pages and word count. If we wanted to add this, we might include a
second constructor that looks like this:
public Book(string title, string author, int pages, int wordCount)
{
this.title = title;
this.author = author;
this.pages = pages;
this.wordCount = wordCount;
}
Methods
The last major category of things to add to a class is an appropriate set of methods. Methods allow
outside code to make requests of the instance. There is no limit to what these requests can entail. It just
depends on what the class is capable of and what other pieces of the system will need from it.
A few possibilities include things like requesting the current state of some part of its data, a request to
change the current state of its data to a new value, or asking it to perform some sort of task.
In this section, we’ll tackle three simple methods, one in in each of those categories. But a full-fledged
Book class might want many more methods than this. A class doesn’t really have a limit to the number of
methods it has (or constructors or instance variables for that matter) but at some point, as the number
gets bigger and bigger, it is probably a sign that the class is responsible for too much, and should be
broken down into smaller pieces.
The three methods we’ll add here are:
1.
2.
3.
A method to retrieve the current title for a book.
A method to specify a new title for the book.
A method that supplies the text of the book and updates the word count accordingly.
This is obviously not a comprehensive list of all methods our Book class could possibly ever want. It is
merely illustrative of how you add methods to a class.
The following code shows a possible implementation for these three methods:
Methods
using
using
using
using
using
119
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace CreatingClasses
{
class Book
{
private string title;
private string author;
private int pages;
private int wordCount;
public Book(string title, string author)
{
this.title = title;
this.author = author;
}
public Book(string title, string author, int pages, int wordCount)
{
this.title = title;
this.author = author;
this.pages = pages;
this.wordCount = wordCount;
}
public string GetTitle()
{
return title;
}
public void SetTitle(string title)
{
this.title = title;
}
public void AssignWordCountFromText(string text)
{
wordCount = text.Split(' ').Length;
}
}
}
We’ve made all of these methods public, which means they can be called from both inside and outside
the Book class. If somebody has a Book instance, then they’ll be able to invoke these methods on it. This
was by design here, but we can have private methods as well, which can be called only from inside the
class. In this case, these become utility methods of the class itself.
You’ll also notice that we have a method that starts with Get and another that starts with Set. These
retrieve (“get”) the value of some piece of data (title in this particular case) and request a new value be
assigned to the data. These are called “getters” and “setters”, and they’re a very common thing to see in a
class. (So much so that we’ll talk about a more powerful way of doing this in Chapter 19). While we only
made a GetTitle and SetTitle, you can easily imagine having a GetAuthor and SetAuthor, a GetPages
and a SetPages, and so on.
You can see that for the GetTitle and SetTitle methods above, they simply pass through to the instance
variable. In this case, you might be tempted to say, “Let’s just make title public, and let people just directly
read from or write to that variable.” The syntax there would definitely be cleaner. But remember that
classes should protect their own data. This protection means keeping the instance variables private in
120
Chapter 18
Making Your Own Classes
most scenarios, and providing alternative means (getters and setters) to make requests instead. This
allows us to change our logic (for example, preventing you from setting the title to null) without breaking
any code that uses the method.
You may have noticed that these methods are missing the static keyword that we’ve been using in the
past. In the next section, we’ll learn what static means and why we didn’t use it here.
The ‘static’ Keyword
In the past, we’ve been putting static on the methods we create, but in the previous section, we
specifically skipped it. So what does static actually mean?
In object-oriented programming, we have classes, which define general behavior for an entire category of
objects, and then we have instances, which are individual occurrences of the category.
If something is marked static, then it means it belongs to the class as a whole—it is shared across all
instances. In fact, in Visual Basic.NET, C#’s evil twin language, they actually use the keyword Shared for
this concept instead, which is a much better way of describing this concept, in my opinion.
While we’ve been using static quite a bit in the past, now that we’re introducing classes, we’re going to
stop using it in most places. In most cases, we don’t actually want to make things static and share them
across all instances of the class.
The static keyword can be applied to any class member, which slight variations in meaning or usage. The
rest of this section will discuss the various ways to use static and its implications.
Static Variables
For a class-level variable, marking it with static means that rather than every instance have its own
variable as defined by a template, there is only a single variable that is shared by all instances and owned
by the class as a whole. Because it is shared, if one instance changes the value of the variable, it will affect
all other instances as well. Applying static to a class-level variable means it is no longer an instance
variable, but a static class variable, or class variable for short. It belongs to the class as a whole, not to any
particular instance of it.
Static Methods
If a method is static then it can be accessed through the class name directly, rather than through any
particular instance of the class, since it belongs to the class as a whole, instead of an instance. To
illustrate, here is how you use a “normal” non-static method:
Book book = new Book("Rendezvous with Rama");
book.AssignWordCountFromText("Sooner or later, it was bound to happen.");
We have an actual instance of the Book class (stored in the book variable) and to invoke the normal nonstatic method, we use the dot operator and call the method from the instance itself.
But if a method is static, then we don’t use an instance. We access it through the class as a whole. We saw
this with the Console class:
Console.WriteLine("Hello World!");
WriteLine is a static method, so we don’t need to create an instance of the Console class. We just invoke
the method directly from the class itself.
There is a time and place for both static and non-static methods. While we’ve been mostly doing static
methods up to this point, normal non-static methods are going to become the new normal. But that
doesn’t completely negate the usefulness of static methods.
Using Our Class
121
A static method does not have access to instance variables, nor can it invoke instance methods.
Static Classes
You can also apply the static keyword to a class. If you do this, then it can’t contain any instance variables
or methods. Everything inside it must be static. Furthermore, if a class is static, then you can’t create an
instance of it either. The compiler will enforce all of this. The Console, Convert, and Math classes are all
static classes. In this sense, static classes are simply a way to group a related collection of utility code.
Static Constructors
While far less useful than some of the other static things we’ve talked about, it is also possible to make a
static constructor. While normal constructors are meant to get a brand new instance into a valid starting
state, a static constructor is meant to get the class as a whole into a valid starting state. This largely only
applies in cases where you have a bunch of static class variables that need to be initialized. (Note that
you can only have one static constructor in a class.) Here is an example:
public class Something
{
public static int sharedNumber;
static Something()
{
sharedNumber = 3;
}
}
When your class is first used, the CLR runtime that is executing your code will pause for a second and look
to see if a class has a static constructor. If it does, it will run it (once and never again during the
application’s lifetime).
Using Our Class
Now that we’ve created a Book class and walked through some of the nuances of creating a class, we
should also look at how to actually use our new class. We saw some of this in the last chapter. Our Book
class is used similarly to the Random class we saw there. In your Main method, you can create an
instance of your Book class, and work with it like this:
Book book = new Book("Harry Potter", "J.K. Rowling");
// Changed my mind. Let's use the full name.
book.SetTitle("Harry Potter and the Half-Blood Prince.");
// Now I forgot. What was the title again?
Console.WriteLine(book.GetTitle());
This is a simple, almost trivial example, but it grows and expands from here. We can use our newly
created class like any other type of data. We create a new instance any time we need to represent a new
book in our system. For any instance, we can call its methods to make requests and interact with it.
Our system will begin to grow. More class definitions will define additional pieces of the larger system.
Instances of the different classes can be combined together to form a cohesive whole, where each class
and each instance is only concerned with a tiny piece of the complete puzzle.
The ‘internal’ Access Modifier
In addition to making things public and private, we can also make things have an “internal” accessibility
level by applying the internal access modifier. The internal modifier is similar to public. It means it can
122
Chapter 18
Making Your Own Classes
be used inside and outside of the class. By contrast though, it indicates that it is only accessible in the
assembly it is defined in. We talked about assemblies back in Chapter 3, but the general idea is that if
something is internal, then it is only accessible in the project that it is defined in.
This is important because things placed directly in a namespace (like a class) default to the internal
accessibility level if nothing else is specified. When we made the Book class in a new file, our class
definition looked like this:
class Book // Or any other name…
{
This class has the internal accessibility level. If you want to be able to use the class outside of the project
where it is defined, you will have to specifically mark it with the public access modifier:
public class Book
{
(I’d also recommend explicitly stating that a class is internal if you truly mean for it to be internal, rather
than ever just leaving it off and being implicitly internal.)
You can also make class members like instance variables and methods internal as well, if the situation is
right.
So when do you use which accessibility level?
The short answer is, you should make everything as restricted (private over internal, and internal over
public) as possible while still allowing things to get done.
The longer answer is a bit more nuanced. Data that belongs to a class should generally be protected by
the class, and so class-level variables should usually be made private.
When it comes to deciding between public and internal, it all comes down to how widely used you
expect the class to be. If you think a class is reusable across many projects, then you might as well make it
public from the beginning. If you mean for the type to be used only inside a single project, it should be
internal. The same rule applies to methods as well.
Class Design and Software Engineering
This chapter has described the basics of building classes. Defining new types like this is the key aspect of
building large products. But while the basic rules of designing and building classes is straightforward,
mastering class design and using it to build complicated software takes a lifetime to master.
The rest of this book will continue to provide you with new tools and techniques for building more
powerful and more interesting classes. But the real learning will take place over the next weeks, months,
and even years as you get more practice and experience with building larger systems out of individual
classes and instances.
As you begin to explore class design, you will sometimes make mistakes and organize your code into the
wrong set of classes. It’s OK to get the design wrong. No matter how much experience you have with class
design, and no matter how many rules and “best practices” you’ve learned, you will still sometimes get it
wrong.
A good programmer will be able to recognize when they’ve accidentally structured their code into the
wrong classes and will refactor and rearrange it so that it is better. As you get more practice, this will
become rarer, but it will never go away completely.
By the time you’ve written 10000 classes, you will have become quite skilled at designing classes.
Class Design and Software Engineering
123
Try It Out!
Designing and Building Classes. Try creating the two classes below, and make a simple program to
work with them, as described below.
Create a Color class:





On a computer, colors are typically represented with a red, green, blue, and alpha
(transparency) value, usually in the range of 0 to 255. Add these as instance variables.
A constructor that takes a red, green, blue, and alpha value.
A constructor that takes just red, green, and blue, while alpha defaults to 255 (opaque).
Methods to get and set the red, green, blue, and alpha values from a Color instance.
A method to get the grayscale value for the color, which is the average of the red, green and
blue values.
Create a Ball class:





The Ball class should have instance variables for size and color (the Color class you just
created). Let’s also add an instance variable that keeps track of the number of times it has
been thrown.
Create any constructors you feel would be useful.
Create a Pop method, which changes the ball’s size to 0.
Create a Throw method that adds 1 to the throw count, but only if the ball hasn’t been
popped (has a size of 0).
A method that returns the number of times the ball has been thrown.
Write some code in your Main method to create a few balls, throw them around a few times, pop a
few, and try to throw them again, and print out the number of times that the balls have been thrown.
(Popped balls shouldn’t have changed.)
Try It Out!
Classes Quiz. Answer the following questions to check your understanding. When you’re done, check
your answers against the ones below. If you missed something, go back and review the section that
talks about it.
1.
2.
3.
4.
5.
6.
7.
8.
True/False. Classes are reference types.
(Classes/Instances) define what a particular type of thing can do and store, while a/an
(class/instance) is a specific object that contains its own set of data.
Name three types of members that can be a part of a class.
True/False. If something is static, it is shared by all instances of a particular type.
What is a special type of method that sets up a new instance called?
Where can something private be accessed from?
Where can something public be accessed from?
Where can something internal be accessed from?
Answers: (1) True. (2) Classes, instance. (3) Instance variables, methods, constructors. (4) True. (5) Constructor. (6) Only
within the class. (7) Anywhere. (8) Anywhere inside of the project it is contained in.
19
19 Properties
In a Nutshell


Properties provide a cleaner approach to creating getters and setters for instance variables.
You can create a property with code like the following:
public int Score
{
get
{
return score;
}
set
{
score = value;
if (score < 0)
score = 0;
}
}




Not all properties need both a setter and a getter.
Getters and setters can have different accessibility levels.
Auto-implemented properties can be created that allow you to quickly define a simple
property with default behavior (public int Score { get; set; }).
Auto-implemented properties can have a default value: public int Score { get; set; } = 10;
In the last chapter, we saw how it is common to have an instance variable and then getter and setter
methods that allow outside code to retrieve and request new values for the variable. This leads to lots of
GetSomething and SetSomething methods. This is so common that C# provides a very powerful feature
that makes it easy to access the value of an instance variable called properties. This chapter will discuss
why properties are so helpful, and several ways to create them.
The Motivation for Properties
Imagine you are creating a simple class to represent a player in a video game, which looks like this so far:
Creating Properties
using
using
using
using
using
125
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace Properties
{
class Player
{
private int score;
}
}
Let’s say that you want to be able to have something outside of the class be able to get and set the value
of the player’s score.
In the last chapter, we learned a way to accomplish this might be by creating a GetScore and a SetScore
method. These two methods can do any special checking that they might need (like ensuring that the
score being set isn’t negative). This might look like this:
public int GetScore()
{
return score;
}
public void SetScore(int score)
{
this.score = score;
if(this.score < 0)
score = 0;
}
But the need for getter and setter methods is extremely common, and making getter and setter methods
is a little cumbersome. Plus, using these methods (e.g., player.SetScore(10);) is rather wordy.
It is tempting to just make our score instance variable public instead of private. Then you could simply
say player.score = 10. That would be very convenient, except for one problem: now the instance variable
is public, and outsiders can intentionally or accidentally put bad data in there. The SetScore method kept
the data protected.
C# has a feature that solves all of these issues, creating a clean way to get or set the value of an instance
variable without publicly exposing it. This feature is called a property.
Creating Properties
A property is simply an easy way to create get and set methods, while still maintaining a high level of
readability. Instead of the two methods that we showed in that last example, we could do this instead:
public int Score
{
get
{
return score;
}
set
{
score = value;
if (score < 0)
score = 0;
}
}
126
Chapter 19
Properties
This creates a Score property. We can see that it is public, so anyone can use it (though we could also use
private if we had wanted). We also specify the type of the property, which in this case is an int. Then we
give it a name.
We then use the get and set keywords to show what should happen when someone tries to read the
value of the property or set the value of the property. The code inside of the curly braces is nearly the
same code that was in the GetScore() and SetScore(int) methods.
With a normal setter method (like SetScore) we would normally have a single parameter that is the new
value being set. In a property, we can’t list parameters. Instead, we’re given exactly one, which we can
access with the keyword value. This takes on the value that the caller is trying to set. The type of value is
the same type as the property. We can call our property using something like this:
Player player = new Player();
int currentScore = player.Score;
player.Score = 50;
When we try to read from the Score property, the code inside of the property’s get block is executed and
returned. When we assign a value to the Score property, the code inside of the set block gets executed. In
this case, the value 50 gets put into the value keyword inside of the set block.
Properties are the best of both worlds. Outsiders get nice clean syntax for getting or setting the data, but
the data isn’t exposed publicly, and is funneled through our logic that allows us to do a sanity check on
the request, rather than blindly allowing outside code to directly manipulate the data.
Interestingly, properties are just syntactic sugar. Behind the scenes, the C# compiler is just turning these
back into normal methods.
Whenever we have a property that gets or sets the value of a specific instance variable, that instance
variable is called a backing field of the property. In our example above, the Score property uses the score
instance variable as its backing field.
Properties are not required to have a backing field. Let’s look at another example where a property does
not have a backing field:
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace Properties
{
class Time
{
private int seconds;
public int Seconds
{
get
{
return seconds;
}
set
{
seconds = value;
}
}
public int Minutes
{
// Seconds is the backing field here...
Different Accessibility Levels
127
get
{
return seconds / 60; // But there's no 'minutes' backing field here.
}
}
}
}
You can see here that we have a seconds instance variable, along with a Seconds property that uses it as
a backing field. (By the way, using lower case names for instance variables, and upper case names for the
matching property is very common.)
In addition, we also have a Minutes property that doesn’t have a backing field at all. Instead, we just do
some other work—in this case, return the number of seconds divided by 60.
Another thing this example shows us is that we don’t necessarily need both set and get blocks for a
property. You can have one or the other or both. If you only have a get block, then the property is readonly, because you can’t set it. This is the case with the Minutes property we just saw. If you have only a
set block, the property would be write-only. (Though write-only properties are rarely actually useful.)
Different Accessibility Levels
So far, our properties have all been public. This means that both the getter and the setter are accessible
to everyone. However, it is possible to make the two have different accessibility levels from each other.
For example, we might want the getter to be public, so that everyone can read it, while making the setter
private, so that it can only be changed from within the class.
To make this happen, you can specify an access modifier before the get or set keywords, like this:
public int Score
{
get // This is public by default, because the property is marked 'public'.
{
return score;
}
private set // This, however, is now private.
{
score = value;
}
}
Auto-Implemented Properties
You will probably find yourself making a lot of things that look something like this:
private int score;
public int Score
{
get
{
return score;
}
set
{
score = value;
}
}
If you have no extra logic that you need to perform in the getter or setter, C# has a feature called an autoimplemented property that does this same thing with far less effort:
128
Chapter 19
Properties
public int Score { get; set; }
This creates a backing field behind the scenes, and simple get and set code. You won’t have direct access
to the backing field in this case. This is a nice shorthand way to create very simple properties.
Read-Only Auto-Implemented Properties
You can also create read-only auto-implemented properties. These can only be written to in a constructor
(or with a default value, which we’ll look at next). After that, their value cannot be changed:
public class Vector2
{
public double X { get; }
public double Y { get; }
public Vector2(double x, double y)
{
X = x;
Y = y;
}
}
It may seem a little odd to restrict a property from being written to. But making all data of a type be readonly after creation (making the type “immutable”) has certain advantages. For instance, you know that if
you’ve got a shared copy of the object that nobody is going to change its values without you knowing
because they simply can’t be changed. Immutability is a broader topic than just read-only properties, and
we’ll discuss it again later through this book.
The key point to remember here is that you can make your auto-implemented property read-only by
simply having only a getter with no setter. This causes it to be writeable while the object is being created,
but not afterwards.
Default Values
For an auto-implemented property, you can also assign a default value to the property:
public class Vector2
{
public double X { get; set; } = 0;
public double Y { get; set; } = 0;
}
Of course, the default value of any number will always be 0, so in this case we haven’t actually changed
anything, but it conveys the idea and the syntax correctly.
You can also supply default values for the read-only properties we just described in the last section.
A default value is assigned before the constructor actually runs. That means that the default value will
already be set by the time the constructor begins, and setting a new value in the constructor will overwrite
the default value.
Object Initializer Syntax
When you’re using an object with properties, you can set values for the properties one at a time. You
frequently do this just after creating a new object. For example:
Book book = new Book();
book.Title = "Frankenstein";
book.Author = "Mary Shelley";
Anonymous Types
129
But C# provides a way to set properties like this more concisely, using a thing called object initializer
syntax. This syntax looks like this:
Book book = new Book() { Title = "Frankenstein", Author = "Mary Shelley" };
The properties all get set on the same line as the object is created. This is frequently more readable than
the earlier option, but not in all cases. Choose the one that makes your code most readable for any given
situation.
Going a bit further, if you use object initializer syntax and you use a parameterless constructor, the
parentheses are (weirdly) optional.
Book book = new Book { Title = "Frankenstein", Author = "Mary Shelley" };
Try It Out!
Playing with Properties. At the end of the last chapter, we created a couple of classes as practice. Go
back to those classes and add properties for the various instance variables that they have. While
doing so, make sure you try at least one auto-implemented property.
Anonymous Types
Now that we understand properties, we can introduce a more advanced (but only occasionally used)
concept called an anonymous type: a class that doesn’t have an actual formal name. To create an instance
of an anonymous type, you use the new keyword, and then make use of the object initializer syntax that
we just looked at:
var anonymous = new { Name = "Steve", Age = 34 };
Console.WriteLine($"{anonymous.Name} is {anonymous.Age} years old.");
This code creates a new instance of an unnamed class with two properties: Name and Age.
Since an anonymous type is anonymous and doesn’t have a name, a variable that stores one of these
must use the var type, as shown above.
It’s also worth pointing out that you can only specify properties for an anonymous type at creation time.
You can’t add more after creation. It is a fixed, unchangeable type, just like any other class we make, just
without the name.
You can’t use an anonymous type anywhere that a type name is required. This includes return values and
parameter types. That means anonymous types can only be used inside of a single method, and can’t be
passed into another, or returned from one.
Giving types names is usually greatly preferred. But on occasion, you have something simple and shortlived enough (contained to a single method or smaller) where an anonymous type could be a decent
solution. We’ll see a few places where this is the case as we continue through this book. Notably, query
expressions (Chapter 38) are a common place to use anonymous types.
20
20 Tic-Tac-Toe
In a Nutshell

This chapter contains a single big project: making a functioning game of Tic-Tac-Toe, including
defining the requirements for the game and my own solution to use as a reference.
We now have all of the knowledge needed to start tackling some larger programs. That is what we will do
in this chapter: make a working game of Tic-Tac-Toe. This particular problems is notably more complicated
than the Try It Out! problems we’ve done so far.
If this book were a course, this would be the mid-term project. If this book were a game, this would be the
Mini-Boss.
Because this is larger than what we’ve done so far, it’s OK if it takes longer to sort through the details and
get it working. (I don’t think you’d be wasting your time if you even spent up to 20 hours working through
this problem if you’re new to programming.)
My solution to this problem is included, along with an analysis of why I did it the way I did, right here in
this chapter. Feel free to peek through what I’ve done as you’re working on your own solution to get some
ideas or help.
Your solution doesn’t need to be the same as mine to be correct. I’ve just made a possible solution. Yours
will inevitably be very different in at least one major way, and slightly different in many more minor ways.
As long as you’ve satisfied the requirements for the Tic-Tac-Toe game listed below, you’re in good shape,
even if it doesn’t match my solution.
You can also download the complete source code for my solution from the book’s website here:
http://starboundsoftware.com/books/c-sharp/try-it-out/tic-tac-toe.
Requirements
The following list contains the requirements for Tic-Tac-Toe:


The game is console-based. (No requirements for a GUI.)
Two human players take turns using the same keyboard.
High-Level Design





131
The players are able to designate which square they want to play in next using the keyboard.
o One possible approach would be to number the squares in the 3x3 board with the digits
1 through 9, like the number pad on the keyboard. If a player enters the number 7, then
the top left corner of the board is chosen.
The game should ensure that only legal moves are made.
o You cannot play in a square that already has something.
o The wrong player cannot take a turn.
o If the player makes an illegal move, the board should remain unchanged.
The game needs to be able to detect when either player wins, or when a draw (“cat”) happens (the
board is full).
Upon detecting that the game is over the outcome is displayed to the user.
Only a single round of play is required.
You might consider something like the following for display of the state of a game:
| X |
---+---+--| O | X
---+---+--O |
|
Try It Out!
Tic-Tac-Toe. Create a Tic-Tac-Toe game that meets the requirements listed in this section. You should
spend a minimum of 20 minutes on this problem (no matter how stuck you get). This is a much bigger
problem than any other Try It Out! so far, so plan on it taking more time. If you find yourself stuck
beyond recovery, read ahead and see if part or all of my solution can help you get unstuck enough to
continue on.
High-Level Design
A Tic-Tac-Toe game is not a huge project compared to some, but it is large enough we can’t (or at least
shouldn’t) dump it all into a single class.
Our first task is to start identifying individual pieces or components in our system that we can turn into
classes. As a general rule, each class we make should have only a single responsibility. It should be doing
only a single thing.
So what kinds of data structures is our game going to need? And what sort of algorithms is our game
going to need?
Here is the high-level breakdown of what I identified myself as candidates for being turned into classes in
the system:
A State Enumeration. Not a class per se, but a type that needs to exist. It makes sense to me to create an
enumeration that lists the states that a single square can be in: X, O, and Undecided (empty). This will be
a helpful building block in the rest of our game.
A Position Class. It made sense to me to make a simple class that could be used to represent a location on
the board, with Row and Column properties.
A Board Class. This class is the fundamental, central data structure for the game as a whole. It contains a
3x3 array of State values to represent the board, along with ways to request the current state of a
position on the board and request placing a new value on the board. In my particular implementation, I
132
Chapter 20
Tic-Tac-Toe
also kept track of whose turn it was and made sure that only the right player could take a turn at any
point in time.
A Player Class. A class that is designed to translate input from the player into a board position to play on.
In our particular case, this will be handling keyboard input, but it isn’t a big stretch to imagine creating
alternate variations of the player that can handle mouse input, a computer AI, or even receiving
instructions over a network. This class will have only one method that will basically be something like:
public Position GetPosition(Board currentState).
A WinChecker Class. This class has the responsibility of analyzing a board and determining if it has
resulted in a win for a player or not yet. It will have a method that looks like this: public State
Check(Board currentState). It will also have a public bool IsDraw(Board currentState) that returns
whether the game has reached a draw. The board itself could probably technically have a method that
does these things, but I elected to pull this logic out into a separate class.
A Renderer Class. This class has the responsibility of drawing the game’s current state, including displaying
the final results of the game when it is over. The Board class could have a method that does this too, but
once again, I chose to pull it out so that I could swap the rendering mechanism (perhaps for a GUI)
without affecting any other part of the code.
A Driver Program. The last piece we’ll need is something that can assemble a functioning game out of all of
these pieces and tie them all together to make it work. This is logic that could technically be done in yet
another class, but in this particular case, I’ve chosen to do this in the Main method in the default
Program class.
Refactoring and Iterative Design
Having enumerated the classes that I put into my own Tic-Tac-Toe implementation, I should state that this
is the final design, not the initial one. It didn’t change drastically, but I did rearrange some pieces and
handle things in different ways than I initially imagined.
Changing the design as you go is not only allowed, it is required. No design will survive first contact with
the codebase. The act of writing code teaches you things about the solution that you hadn’t considered.
Always be prepared to change your code as you go.
This process of reshaping and reforming the code is called refactoring, and is a key part of software
development. Your initial design will never be quite right. Plan on going through several iterations to
refine your design, even though that sometimes means working on code refactoring instead of adding
new features. It is just part of life as a software developer.
The Full Solution
We now shift away from the high level design to the specifics of each type in our Tic-Tac-Toe game. Each of
the following sections starts with the code I made for the piece indicated by its title. Following that is a
brief description on some of the complexities as well as a bit of information on why I implemented it the
way I did.
The State Enumeration
The following is the code for the State enumeration:
public enum State { Undecided, X, O };
This enumeration is straightforward, and just lists the options available. I put Undecided first because
that makes it the default value, which simplifies the board initialization later on.
The Full Solution
133
The Position Class
The following is the code for the Position class:
public class Position
{
public int Row { get; }
public int Column { get; }
public Position(int row, int column)
{
Row = row;
Column = column;
}
}
This class is also quite simple. It just defines Row and Column properties. I have intentionally made this
class immutable—everything in it is read-only, once it gets past creation time. Immutability has a number
of advantages, including knowing that you can be certain nobody is going to modify it after the fact. (They
can create a new instance to place in a variable, but cannot modify an instance.)
This class doesn’t bother verifying that the row and column are legitimate locations on the board. It
wouldn’t be unreasonable to do so, but I opted not to.
The Board Class
The Board class is substantially trickier:
public class Board
{
private State[,] state;
public State NextTurn { get; private set; }
public Board()
{
state = new State[3, 3];
NextTurn = State.X;
}
public State GetState(Position position)
{
return state[position.Row, position.Column];
}
public bool SetState(Position position, State newState)
{
if (newState != NextTurn) return false;
if (state[position.Row, position.Column] != State.Undecided) return false;
state[position.Row, position.Column] = newState;
SwitchNextTurn();
return true;
}
private void SwitchNextTurn()
{
if (NextTurn == State.X) NextTurn = State.O;
else NextTurn = State.X;
}
}
The central aspect of this class is the State[,] state instance variable. Everything else is built around that.
(The other property in this class is the NextTurn property, which determines whose turn is next.)
134
Chapter 20
Tic-Tac-Toe
Since we don’t want other parts of the system to be able to reach in and directly manipulate the current
state of the board, we provide a getter and setter method for the state of any spot on the board. I would
have used a property, but I needed to know what position on the board we were asking about. Properties
don’t support supplying a parameter like that, so I had to fall back to plain getter and setter methods with
the position supplied directly as a parameter.
The SetState method is somewhat tricky. If you are trying to go out of turn, it rejects you. If you are trying
to play in a square that is already filled, it rejects you. Besides just setting the new state, this method also
does maintenance work to make sure that it flips the next turn over to the other player.
The WinChecker Class
The WinChecker class has the logic to determine if (a) either X or O has won, and (b) if the board is full,
resulting in a draw:
public class WinChecker
{
public State Check(Board board)
{
if (CheckForWin(board, State.X)) return State.X;
if (CheckForWin(board, State.O)) return State.O;
return State.Undecided;
}
private bool CheckForWin(Board board, State player)
{
for (int row = 0; row < 3; row++)
if (AreAll(board, new Position[] {
new Position(row, 0),
new Position(row, 1),
new Position(row, 2) }, player))
return true;
for (int column = 0; column < 3; column++)
if (AreAll(board, new Position[] {
new Position(0, column),
new Position(1, column),
new Position(2, column) }, player))
return true;
if (AreAll(board, new Position[] {
new Position(0, 0),
new Position(1, 1),
new Position(2, 2) }, player))
return true;
if (AreAll(board, new Position[] {
new Position(2, 0),
new Position(1, 1),
new Position(0, 2) }, player))
return true;
return false;
}
private bool AreAll(Board board, Position[] positions, State state)
{
foreach(Position position in positions)
if (board.GetState(position) != state) return false;
return true;
}
The Full Solution
135
public bool IsDraw(Board board)
{
for (int row = 0; row < 3; row++)
for (int column = 0; column < 3; column++)
if (board.GetState(new Position(row, column)) == State.Undecided) return false;
return true;
}
}
This code isn’t quite as bad as it looks. The bulk of it is in checking to see if a win has been reached along
the rows, the columns, and then the diagonals. This same logic is reused for both X and O, so it is called
from the main Check method for each player. I pulled out the logic to actually iterate through all positions
to see if they’re equal to a separate method (AreAll).
The IsDraw method at the bottom simply computes whether there the board is a draw or not yet, which
is defined as it not being a draw if we can find a single cell that isn’t full. If we can’t, it is a draw.
The Renderer Class
The following code is for the Renderer class:
public class Renderer
{
public void Render(Board board)
{
char[,] symbols = new char[3, 3];
for (int row = 0; row < 3; row++)
for (int column = 0; column < 3; column++)
symbols[row, column] = SymbolFor(board.GetState(new Position(row, column)));
Console.WriteLine($" {symbols[0, 0]} | {symbols[0, 1]} | {symbols[0, 2]} ");
Console.WriteLine("---+---+---");
Console.WriteLine($" {symbols[1, 0]} | {symbols[1, 1]} | {symbols[1, 2]} ");
Console.WriteLine("---+---+---");
Console.WriteLine($" {symbols[2, 0]} | {symbols[2, 1]} | {symbols[2, 2]} ");
}
private char SymbolFor(State state)
{
switch(state)
{
case State.O: return 'O';
case State.X: return 'X';
default: return ' ';
}
}
public void RenderResults(State winner)
{
switch (winner)
{
case State.O:
case State.X:
Console.WriteLine(SymbolFor(winner) + " Wins!");
break;
case State.Undecided:
Console.WriteLine("Draw!");
break;
}
}
}
There are two public methods in this, and one utility method. Render builds a 3x3 array of what symbols
should be used for each slot in the display, then uses a heavy dose of string interpolation to draw the
136
Chapter 20
Tic-Tac-Toe
board. RenderResults just prints out who won. They both draw on the SymbolFor utility method, which
simply converts a State to a char. Interestingly, this allows me to change the symbols for X and O in only
one place. (We could play 1’s and 0’s instead of X’s and O’s, for example.)
The Player Class
The following is the code for the Player class:
public class Player
{
public Position GetPosition(Board board)
{
int position = Convert.ToInt32(Console.ReadLine());
Position desiredCoordinate = PositionForNumber(position);
return desiredCoordinate;
}
private Position PositionForNumber(int
{
switch (position)
{
case 1: return new Position(2,
case 2: return new Position(2,
case 3: return new Position(2,
case 4: return new Position(1,
case 5: return new Position(1,
case 6: return new Position(1,
case 7: return new Position(0,
case 8: return new Position(0,
case 9: return new Position(0,
default: return null;
}
}
position)
0);
1);
2);
0);
1);
2);
0);
1);
2);
//
//
//
//
//
//
//
//
//
Bottom Left
Bottom Middle
Bottom Right
Middle Left
Middle Middle
Middle Right
Top Left
Top Middle
Top Right
}
In a nutshell, this simply gets a number from the user (ignoring bad input) and then converts a digit to a
Position object. The positions are arranged like the number pad on a keyboard.
It is worth noting that the GetPosition method never actually uses the board parameter it is given, and
could have been skipped. I opted to include it, partly because I’m thinking about how we could extend the
game to include other types of players, especially a computer AI player. An AI would definitely make use
of this parameter, since it can’t see the screen to make its decisions. Structuring my Player class and its
GetPosition method this way allows me to switch to a completely different implementation of Player
without requiring the rest of the program to change how they interact with it. (We haven’t talked about
interfaces yet (Chapter 24) but I’d be using one here if we had.
The Driver Program
The last part is the driver program, which assembles everything else together and runs the game:
public class Program
{
static void Main(string[] args)
{
Board board = new Board();
WinChecker winChecker = new WinChecker();
Renderer renderer = new Renderer();
Player player1 = new Player();
Player player2 = new Player();
while(!winChecker.IsDraw(board) && winChecker.Check(board) == State.Undecided)
{
renderer.Render(board);
The Full Solution
137
Position nextMove;
if (board.NextTurn == State.X)
nextMove = player1.GetPosition(board);
else
nextMove = player2.GetPosition(board);
if (!board.SetState(nextMove, board.NextTurn))
Console.WriteLine("That is not a legal move.");
}
renderer.Render(board);
renderer.RenderResults(winChecker.Check(board));
Console.ReadKey();
}
}
The first block of lines sets up the pieces of our game: a board, a win checker, a renderer, and two players.
The second piece is the “game loop” that makes our game tick. It draws the board, asks for a move from
the current player, and updates the board. When the game has become a draw or a win for somebody,
the loop ends, the results are displayed, and the program terminates.
This could have been a separate class, and would probably make sense to do so if we wanted to be able
to play multiple rounds or configure rounds differently (like giving the player the option of playing a twoplayer game with two humans, or a one player game against the computer).
Wrapping Up
This covers my implementation of this Tic-Tac-Toe problem. The full solution can be found on the book’s
website if you want to download and run it: http://starboundsoftware.com/books/c-sharp/try-itout/tic-tac-toe.
Again, my solution is one possible solution. It is not meant to be definitive, only to help you get unstuck if
you run into problems, or illustrate a second alternative approach.
21
21 Structs
In a Nutshell




A struct or structure is similar to a class in terms of the way it is organized, but a struct is a
value type, not a reference type.
Structs should be used to store compound data (composed of more than one part) that does
not involve a lot of complicated methods and behaviors.
All of the simple types are structs.
The primitive types are all aliases for certain pre-defined structs and classes.
A few chapters ago we introduced classes. These are complex reference types that you can define and
build from the ground up. C# has a feature call structs or structures which look very similar to classes
organizationally, but they are value types instead of reference types.
In this chapter, we’ll take a look at how to create a struct, as well as discuss how to decide if you need a
struct or a class. We’ll also discuss something that may throw you for a loop: all of the built-in types like
bool, int, and double, are actually all aliases for structures (or a class in the case of the string type).
Creating a Struct
Creating a struct is very similar to creating a class. The following code defines a simple struct, and an
identical class that does the same thing:
struct TimeStruct
{
private int seconds;
public int Seconds
{
get { return seconds; }
set { seconds = value; }
}
public int CalculateMinutes()
{
return seconds / 60;
Structs vs. Classes
139
}
}
class TimeClass
{
private int seconds;
public int Seconds
{
get { return seconds; }
set { seconds = value; }
}
public int CalculateMinutes()
{
return seconds / 60;
}
}
You can see that the two are very similar—in fact the same code is used in both, with the single solitary
difference being the struct keyword instead of the class keyword.
Structs vs. Classes
Since the two are so similar in appearance, you’re probably wondering how the two are different.
The answer to this question is simple: structs are value types, while classes are reference types. If you
didn’t fully grasp that concept back when we discussed it in Chapter 16, it is probably worth going back
and taking a second look.
While this is a single difference in theory, this one change makes a world of difference. For example, a
struct uses value semantics instead of reference semantics. When you assign the value of a struct from
one variable to another, the entire struct is copied. The same thing applies for passing one to a method as
a parameter, and returning one from a method.
Let’s say we’re using the struct version of the TimeStruct we just saw, and did this:
public static void Main(string[] args)
{
TimeStruct time = new TimeStruct();
time.Seconds = 10;
UpdateTime(time);
}
public static void UpdateTime(TimeStruct time)
{
time.Seconds++;
}
In the UpdateTime method, we’ve received a copy of the TimeStruct. We can modify it if we want, but
this hasn’t changed the original version, back in the Main method. We’ve modified a copy, and the original
still has a value of 10 for seconds.
Had we used TimeClass instead, handing it off to a method copies the reference, but that copied
reference still points the same actual object. The change in the UpdateTime method would have affected
the time variable back in the Main method.
Like I said back when we were looking at reference types, this can be a good thing or a bad thing,
depending on what you’re trying to do, but the important thing is that you are aware of it.
140
Chapter 21
Structs
Interestingly, while we get a copy of a value type as we move it around, it doesn’t necessarily mean we’ve
completely duplicated everything it is keeping track of. Let’s say you had a struct that contained within it a
reference type, like an array, as shown below:
struct Wrapper
{
public int[] numbers;
}
And then we used it like this:
public static void Main(string[] args)
{
Wrapper wrapper = new Wrapper();
wrapper.numbers = new int[3] { 10, 20, 30 };
UpdateArray(wrapper);
}
public void UpdateArray(Wrapper wrapper)
{
wrapper.numbers[1] = 200;
}
We get a copy of the Wrapper type, but for our numbers instance variable, that’s a copy of the reference.
The two are still pointing to the same actual array on the heap.
Tricky little things like this are why if you don’t understand value and reference types, you’re going to get
bit by them. If you’re still fuzzy on the differences, it’s worth a second reading of Chapter 16.
There are other differences that arise because of the value/reference type difference:


Structs can’t be assigned a value of null, since null indicates a reference to nothing.
Because structs are value types, they’ll be placed on the stack when they can. This could mean
faster performance because they’re easier to get to, but if you’re passing them around or
reassigning them a lot, the time it takes to copy them could slow things down.
Another big difference between structs and classes is that in a struct, you can’t define your own
parameterless constructor. For both classes and structs, if you don’t define any constructors at all, one
still exists: a default parameterless constructor. This constructor has no parameters, and is the simplest
way to create new objects of a given type, assuming there’s no special setup logic required.
With classes, you can create your own parameterless constructor, which then allows you to replace the
default one with your own custom logic. This cannot be done with structs. The default parameterless
constructor creates new objects where everything is zeroed out. All numbers within the struct start at 0,
all bools start at false, all references start at null, etc. While you can create other constructors in your
struct, you cannot create a parameterless one to replace this default one.
Deciding Between a Struct and a Class
Despite the similarities in appearance, structs and classes are made for entirely different purposes. When
you create a new type, which one do you choose? Here are some things to think about as you decide.
For starters, do you have a particular need to have reference or value semantics? Since this is the primary
difference between the two, if you’ve got a good reason to want one over the other, your decision is
basically already made.
If your type is not much more than a compound collection of a small handful of primitives, a struct might
be the way to go. For instance, if you want something to keep track of a person’s blood pressure, which
Prefer Immutable Value Types
141
consists of two integers (systolic and diastolic pressures) a struct might be a good choice. On the other
hand, if you think you’re going to have a lot of methods (or events or delegates, which we’ll talk about in
Chapters 32 and 33) then you probably just want a class.
Also, structs don’t support inheritance which is something we’ll talk about in Chapter 22, so if that is
something you may need, then go with classes.
In practice, classes are far more common, and probably rightly so, but it is important to remember that if
you choose one way or the other, and then decide to change it later, it will have a huge ripple effect
throughout any code that uses it. Methods will depend on reference or value semantics, and to change
from one to the other means a lot of other potential changes. It’s a decision you want to make
consciously, rather than just always defaulting to one or the other.
Prefer Immutable Value Types
In programming, we often talk about types that are immutable, which means that once you’ve set them
up, you can no longer modify them. (As opposed to mutable types, which you can modify parts of its data
on the fly.) Instead, you would create a new copy that is similar, but with the changes you want to make.
All of the built-in types (including the string type, which is a reference type) are immutable.
There are definite benefits to making both value and reference types immutable, but especially so with
structs. This is because we think of value types like structs as a cohesive specific value. Because it has
value semantics (copies of the whole thing are made, rather than just making a second reference to the
same actual bytes in memory) we end up duplicating value types all over the place.
If we aren’t careful, with a mutable, changeable value type, we might think we’re modifying the original,
but are instead modifying the original.
For example, what will the following code output?
struct S
{
public int Value { get; set; }
}
class Program
{
static void Main(string[] args)
{
S[] values = new S[10];
S item = values[0];
item.Value++;
Console.WriteLine(values[0].Value);
}
}
//
//
//
//
New array of structs with default values is created here.
Danger! Copy is made here.
Copy is modified here.
Original, unmodified value is printed here.
It actually prints out 0. This might come as a surprise. This is because the line that says S item =
values[0]; produces a copy for the assignment. So when you do item.Value++, you are modifying the
copy, not the original. (This would not be true if S were a class instead of a struct.)
If we make S immutable so you can’t modify its Value property at all, then the only way to produce a new
version with the correctly incremented value would be to create a new S object, populated with the
correct value at construction time. (You would want to define a constructor that allows you to specify
value at creation time if you do this.)
At this point, that item.Value++ line would have to become item = new S(item.Value + 1);, and the error
becomes much more obvious to spot.
142
Chapter 21
Structs
Making things in general immutable has many benefits, but for structs, you should definitely have a
preference for making them immutable. (Sometimes the overhead performance cost associated with
creating lots of objects will supersede the usefulness of immutable types, for example.)
The Built-In Types are Aliases
Back in Chapter 6, we took a look at all of the primitive or built-in types that C# has. This includes things
like int, float, bool, and string. In Chapter 16, we looked at value types vs. reference types, and we
discovered that these primitive types are value types, except for string, which is a reference type.
In fact, more than just being value types, they are actually structs! This means that everything we’ve been
learning about structs also applies to these built-in types.
Even more, all of the primitive types are aliases for other structs (or class, in the case of the string type).
We’ve been working with things like the int type. But behind the scenes the C# compiler is simply
changing this over to a struct that is defined in the same kind of way that we’ve seen here. In this case, it
is the Int32 struct (System.Int32).
So while we’ve been writing code that looks like this:
int x = 0;
We could have also used this:
Int32
Int32
int z
int w
x
y
=
=
= new Int32();
= 0;
new Int32();
new int();
// Or combined.
// Or combined another way. It's all the same thing.
// Yet another way...
The following table identifies the aliases for each of the built-in types:
Primitive Type
Alias For:
bool
System.Boolean
byte
System.Byte
sbyte
System.SByte
char
System.Char
decimal
System.Decimal
double
System.Double
float
System.Single
int
System.Int32
uint
System.UInt32
long
System.Int64
ulong
System.UInt64
object
System.Object
short
System.Int16
ushort
System.UInt16
string
System.String
With only a few of exceptions, the “real” struct name is the same as the keyword version, just with
different capitalization. Keywords in C# are all lowercase by convention, but nearly everybody will
capitalize type names, which explains that difference.
The Built-In Types are Aliases
143
You’ll also see that instead of short, int, and long, the structs use Int followed by the number of bits they
use. It explicitly states exactly how many bits are in each type, which gives some clarity.
Additionally, float becomes Single rather than Float. Technically, float, double, and decimal are all
floating point types. But double has twice the bits as float, so the term “single” is a more specific (and
therefore technically more accurate) name for it. The C# language designers stuck with float because
that’s the keyword that is used for this data type in many other languages.
Try It Out!
Structs Quiz. Answer the following questions to check your understanding. When you’re done, check
your answers against the ones below. If you missed something, go back and review the section that
talks about it.
1.
2.
3.
4.
5.
Are structs value types or reference types?
True/False. It is easy to change classes to structs, or structs to classes.
True/False. Structs are always immutable.
True/False. Classes are never immutable.
True/False. All primitive/built-in types are structs.
Answers: (1) Value types. (2) False. (3) False. (4) False. (5) False. string and object are reference types.
22
22 Inheritance
In a Nutshell




Inheritance is a way to reuse classes by expanding them into more specific types of classes.
For instance, a Square class can be derived from (or inherit from) a Polygon class, giving it all
of the features that the Polygon class has.
Any class can be used as a base class, except for sealed classes.
To indicate a class is derived from another, you use the colon followed by the base class
name. For example class Square : Polygon { /* ... */ }.
The protected access modifier means that anything within the class can use it, as well as
anything in any derived class.
Imagine that you are trying to keep track of geometric polygons, like triangles, rectangles, squares,
pentagons, and so on. You can probably imagine creating a Polygon class that stores the number of sides
the polygon has, and maybe a way to represent the positions of the vertices (corners) of the polygon.
Your Polygon class could be used to represent any of these polygons, but let’s imagine that for a square,
you want to be able to do some extra things with it. For example, you might want to create one using only
a size (since the width and height are the same length in a square), or maybe you want to add a method
that returns the area of the square.
One approach would be to create a Square class that duplicates everything in the Polygon class, plus
adds the extra stuff you want as well. The problem with this is that now you have two entirely different
classes, and if you want to make a change in the way people work with polygons, you’ll also need to make
a change in the way they work with squares. More than that, while you could create an array of polygons
(Polygon[] polygons) you couldn’t put squares in that list, even though in your head, they are polygons,
and should be able to be treated as polygons.
After all, a square is just a special type of a polygon.
And in fact, that is getting right to the heart of what we’re going to discuss in this chapter. A square is a
polygon, but more specifically, it is a special type of polygon that has its own special things. Anything a
polygon can do, a square can do too, because a square is a polygon.
Base Classes
145
This is what programmers call an is-a relationship, or an is-a-special-type-of relationship. It is so common
that object-oriented programming languages have a special way to facilitate this kind of thing.
This is called inheritance. Inheritance allows us to create a Polygon class, and a Square class that is based
on Polygon, This makes it inherit (or reuse) everything from the Polygon class that it is based on. Any
changes we make to the Polygon class will also be automatically changed in the Square class.
Classes support inheritance, but structs and other types do not. It is a key difference between the two.
Base Classes
A base class (or superclass or parent class) is any normal class that happens to be used by another for
inheritance. In our discussion, the Polygon class would be a base class.
For instance, we could have a class that looks like this:
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
namespace Inheritance
{
// Nothing special about this class. It will be used as the base class.
class Polygon
{
public int NumberOfSides { get; set; }
public Polygon()
{
NumberOfSides = 0;
}
public Polygon(int numberOfSides)
{
NumberOfSides = numberOfSides;
}
}
}
Derived Classes
A derived class (or subclass) is one that is based on, and expands upon another class. In our example, the
Square class is a derived class that is based on the Polygon class. We would say that the Square class is
derived from the Polygon class.
You can create a derived class just like any other class with one small addition to indicate which class is its
base class. To indicate that the Square class is derived from the Polygon class, we use the colon (‘:‘) and
name the base class:
class Square : Polygon
{
//...
}
// Inheritance!
Inside of the class, we simply indicate any new stuff that the Square class has that the Polygon class
doesn’t have. Our completed Square class might look like this:
using System;
using System.Collections.Generic;
using System.Linq;
146
Chapter 22
Inheritance
using System.Text;
namespace Inheritance
{
class Square : Polygon
{
public float Size { get; set; }
public Square(float size)
{
Size = size;
NumberOfSides = 4;
}
}
}
So now a square will have a Size property, but in addition, it inherits instance variables, properties, and
methods that were in the Polygon class. So the Square class also has a NumberOfSides property that it
inherits from the Polygon class.
One other thing that we should mention here is that a class can inherit from a class that inherits from
another class. You’re allowed to have as many layers of inheritance as you want. (Though deep hierarchies
have their own set of problems.)
Using Derived Classes
Derived classes are just like any other class, and can be used as a type just like any other class. From a
basic perspective, there’s nothing special about how you use them:
Polygon polygon = new Polygon(3); // a triangle
Square square = new Square(4.5f); // a square, which is a polygon with 4 sides of length 4.5.
But here’s the interesting thing. Because the computer knows that a Square is just a special type of
Polygon, you can actually use the Square type anywhere that you could use a Polygon type. After all, a
Square is a Polygon!
Polygon polygon = new Square(4.5f);
As the program is running, when we’re using the polygon variable, we only have access to the stuff that a
Polygon has. So we can check to see the NumberOfSides it has. But as the program is running, we can
only treat it as a Polygon. There’s a chance that it is a Square (or another derived class), but the computer
won’t assume it is. So we won’t be able to automatically use stuff created in the Square class in this
particular case. Because our variable uses the Polygon type, we can only work with the things the
Polygon type defines.
While we can put a Square in a variable with the Polygon type, you can’t go the other way around. You
can’t assign a Polygon object to a variable with the type Square, like this:
Square square = new Polygon(3);
// Does not compile.
This is because while all squares are polygons, not all polygons are squares.
Checking an Object’s Type and Casting
As I just described, it is possible to be actually using an instance of a derived type like the Square, but only
know that it is a Polygon. Sometimes we want to be able to figure out what type an object is specifically,
and work with it differently.
It is possible to check to see if an object is a specific type, with the is keyword and casting:
Constructors and Inheritance
147
Polygon polygon = new Square(4.5f);
if (polygon is Square)
{
Square square = (Square)polygon;
// We can now do what we want with the square.
}
The ‘as’ Keyword
In addition to casting, there is another way we can convert one object type to another. This is done by
using the as keyword, which looks like this:
Polygon polygon = new Square(4);
Square square = polygon as Square;
There are a couple of differences between using the as keyword and casting, and it turns out, using the as
keyword in this type of a situation is usually a better choice.
To fully understand this, let’s assume that in addition to the Polygon and Square classes we’ve been
looking at, there is a Triangle class as well. Let’s say we have a variable with the Polygon type, and assign
it a new Triangle instance, but then we inadvertently try to turn it into a Square:
Polygon polygon = new Triangle();
Square square = (Square)polygon;
In this case, the program will crash, giving us an error that says it was an invalid cast. On the other hand, if
we used the as keyword, instead of crashing, we would get null, which we can then respond to:
Polygon polygon = new Triangle();
Square square = polygon as Square;
if(square != null)
{
// ...
}
Using Inheritance in Arrays
In light of what we’ve just been discussing, it is worth mentioning that you can create an array of the base
class and put any derived class inside of it. This is just like what we already saw with variables that weren’t
arrays, so it shouldn’t be too surprising:
Polygon[] lotsOfPolygons = new Polygon[5];
lotsOfPolygons[2] = new Square(2.1f);
lotsOfPolygons[3] = new Triangle();
In any case where a base class can be used, you can always substitute a derived class.
Constructors and Inheritance
I mentioned earlier that in a derived class, all properties, instance variables, and methods are inherited.
Constructors, on the other hand, are not.
You can’t use the constructors in the base class to create a derived class. For instance, our Polygon class
has a constructor that takes an int that is used for the polygon’s number of sides. You can’t create a
Square using that constructor. And for good reason. Remember, constructors are special methods that
are designed to outline how to build new instances of a particular type. With inheritance, a constructor in
the base class doesn’t have any clue how to set up the new parts of the derived class. To create an
instance of a class, you must use one of the constructors that are defined in that specific class.
148
Chapter 22
Inheritance
When a derived class defines a constructor, it needs to call one of the constructors from the base class. By
default, a constructor in the derived class will use the base class’s parameterless constructor. (Remember,
if you don’t define any constructor yourself, a default parameterless constructor will be created for you.) If
the base class doesn’t have a parameterless constructor, or if you would like a constructor in the derived
class to use a different one, you will need to indicate which constructor to use. This is done by using a
colon and the base keyword along with the parameters for the constructor we want to use, like this:
public Square(float size) : base(4) // Uses the Polygon(int numberOfSides) constructor in the base class.
{
Size = size;
}
The ‘protected’ Access Modifier
In the past, we discussed the private and public access modifiers. To review, remember that public
meant anyone could get access to the member (variable, method, property, etc.), while private means
that you only have access to it from inside of the class that it belongs to.
With inheritance we add another option: protected. If a member of the class uses the protected
accessibility level, then anything inside of the class can use it, as well as any derived class. It’s a little
broader than private, but still more restrictive than public.
If the Polygon class made the NumberOfSides property protected instead of public, then you could
access it from anywhere in the Polygon class, as well as the Square class, and any other derived class, but
not from outside of those.
The Base Class of Everything: object
Without specifying any class to inherit from, any class you make still is derived from a special base class
that is the base class of everything. This is the object class. All classes are derived from this class by
default, unless you state a different class. But even if you state a different class as the base class, that
class will be derived from the object class. If you go up the inheritance hierarchy, everything always gets
back to the object class eventually.
You can use this type in your code using the object keyword (which is an alias for System.Object).
Remember what I said earlier about how you can store a derived class in a base type like the code below?
Polygon polygon = new Square(4.5f);
You can also put it in the object type:
object anyOldObject = new Square(4.5f);
Since the object type is the base class for all classes, any and every type of object can be stored in it.
Sealed Classes
There may come a time where you want to prevent a class from being inherited. This might happen if you
want to ensure that no one derives anything from a specific class.
You can use the sealed keyword to make it so nothing can inherit from a specific class by adding it to your
class definition:
sealed class Square : Polygon
{
// ...
}
Partial Classes
149
Sealing a class can also result in a performance boost.
Partial Classes
A class can be split across multiple files or sections using the partial keyword:
public partial class SomeClass
{
public void DoSomething()
{
}
}
public partial class SomeClass
{
public void DoSomethingElse()
{
}
}
Even if the two parts are in different files, they’ll be combined into one when you compile your program.
You can also use the partial keyword on structs and interfaces (Chapter 24).
While you can use partial classes to split up a very large class into more manageable files, this is not the
purpose of partial classes. In those cases, you should look for ways to split it into multiple classes.
The real purpose of partial classes is to split along “owner” boundaries. For example, UI frameworks will
frequently have a GUI design tool, where you drag and drop buttons, checkboxes, etc. onto an editable
canvas. The design tool might then be responsible for one part, leaving another part for the programmer
to work in. The design tool won’t ever accidentally overwrite stuff the programmer did, or vice versa.
Partial Methods
While practical uses are very limited, you can also have partial methods. These are interesting because a
partial method is defined in one portion of the class, but is then optionally implemented elsewhere.
The code below illustrates how this works, though like with any partial class, in practice, the two pieces
likely live in separate files, unlike the code below shows:
public partial class SomeClass
{
partial void Log(string message);
}
public partial class SomeClass
{
partial void Log(string message)
{
Console.WriteLine(message);
}
}
It shouldn’t be a surprise to hear that partial methods can only go inside partial classes or structs.
It also shouldn’t be a surprise that the signatures must match (same number of parameters and same
type of parameters in the same order, as well as the same method name and return type).
What might be a little weird is that you cannot put an access modifier (like public or private) on the
method—they are implicitly private, and can’t be changed from that.
Also noteworthy is that their return type must be void. That is related to what a partial method does,
which we’ll discuss in just a moment.
150
Chapter 22
Inheritance
Remember that partial methods are optionally implemented. That means that the following code will also
compile:
public partial class SomeClass
{
partial void Log(string message);
}
public partial class SomeClass
{
// Intentionally missing implementation of Log.
public void DoStuff()
{
Log("I did stuff."); // But invoking the partial method anyway.
}
}
Does that seem weird? It probably should. At least a little.
What if it is never implemented, but gets called somewhere, like in the code above? What happens then?
This is where the purpose of partial methods comes into play: if a partial method is not implemented,
then it gets cut out at compile time. Any place that invokes it is skipped by the compiler. Flat out removed.
The purposes for this are rather limited. It requires a scenario in which parts of the class are defined by
one thing (typically some auto-generated partial class from Visual Studio) and wants to be able to expect
certain methods are available to call, but where it doesn’t want another thing (typically you, the
programmer) to be required to implement it. You will rarely encounter the need for a partial method, and
virtually never need to write both pieces of the partial method.
C# Does Not Support Multiple Inheritance
Some object-oriented programming languages (C++, for example) allowed you to choose more than one
base class to derive from. There are some benefits to this, because it allows one type of object to have an
is-a relationship with more than one other type. For example, you could have a PocketKnife class that isa Knife and is-a-really-crappy Saw, among other things.
Unfortunately, having multiple inheritance complicates things, and leads to situations where it is
ambiguous how the class should behave. Because of this, C# does not to allow multiple inheritance. You
must pick only one base class.
You can sometimes mimic multiple inheritance by using interfaces, which we’ll talk about in Chapter 24.
Try It Out!
Inheritance Quiz. Answer the following questions to check your understanding. When you’re done,
check your answers against the ones below. If you missed something, go back and review the section
that talks about it.
1.
2.
3.
4.
5.
6.
True/False. A derived class adds functionality on to a base class.
True/False. Everything that is in a base class can be accessed by the derived class.
True/False. Structs support inheritance.
True/False. All classes are derived from the object class.
True/False. A class can be a derived class and a base class at the same time.
True/False. You can prevent a class from being used as a base class.
Answers: (1) True. (2) False. Not private members. (3) False. (4) True. (5) True. (6) True.
23
23 Polymorphism, Virtual
Methods, and Abstract
Classes
In a Nutshell





Polymorphism means that you can create derived classes that implement the same method
in different ways from each other. This allows different classes to do the same task in their
own custom way.
A method in a base class can be marked virtual, allowing derived classes to use the override
keyword and provide an alternative implementation.
Classes can be marked as abstract, making it so you can’t actually create an instance of the
class. You have to create an instance of a derived class instead.
In an abstract class, you can mark methods as abstract as well, and then leave out the
method implementation. Derived classes will need to provide an implementation for any
abstract methods.
The new keyword, when attached to a method, means that a new method is being created,
unrelated to any methods in the base class with the same name, resulting in two methods
with the same name, with the one in the base class being hidden.
In the last chapter, we talked about how it is possible to create a class that is based on another, adding
more stuff to it. It turns out inheritance lets us do a lot more than just add new things to an existing class.
In this chapter, we’ll look at how a derived class can provide its own implementation for a method that
was defined in the base class, allowing it to handle the same task in its own way.
Polymorphism
“We do things differently around here.” Maybe you’ve heard people say things like that before. What
they’re implying is they do the same kind of thing as somebody else, but it is done in a better way.
152
Chapter 23
Polymorphism, Virtual Methods, and Abstract Classes
Take a web search for instance. Everybody knows about Google, but there are plenty of other web-based
search engines, like Bing and Yahoo! Search. All of them do the same basic task, but each of them do it in
their own way. You give them a word, they go off and dig around in their notes that’s they’ve been
furiously taking as they frantically searched for the end of the Internet, and give you back a list of anything
that could be useful.
This is an excellent example of something programmers call polymorphism, a fancy Greek word meaning
“many forms.” Polymorphism is a feature that allows derived classes to override or change the way the
base class does things, and do them in their own way. Alternatively, instead of changing the way the base
class does things, the base class may not even have a way to do things, forcing derived classes to find
their own way to do things. But when all is said and done, these derived classes will have a different way
of doing things from the base class and from each other. The fact that these classes can do the same task
differently (while still calling the same method) is what gives us “many forms” and polymorphism.
In the last chapter, with our basic inheritance model, derived classes could only add things to a type. With
polymorphism, we can do more than just add stuff. We can also alter how the derived class will handle
the things the base class supplies.
Going with our search engine example, we might have a SearchEngine class like this:
class SearchEngine
{
public virtual string[] Search(string findThis)
{
return new string[0]; // I'm terrible at searching... I give up.
}
}
Our SearchEngine class happens to define a single method. Here in the base class, we’ve provided a
default (and terrible) implementation of the Search method.
You’ll also notice the virtual keyword. This is what gives derived classes permission to change the way this
method works. If you want to allow a derived class to change the way a method is used, you simply need
to stick this on your method. (This also can be used for properties, as well as indexers and events, which
we haven’t discussed yet.)
In a derived class, we now have the option to provide an alternative implementation for the method:
class GoogleSearch : SearchEngine
{
public override string[] Search(string findThis)
{
// Google Search is, of course, way better than 3 hard-coded results like this...
return new string[] {
"Here are some great results.",
"Aren't they neat?",
"I found 1.2 billion things, but you will only look at the first 10." };
}
}
To change the implementation for the derived class, we simply create a new copy of the method, along
with our new custom-made implementation, and add in the override keyword.
If the base class didn’t use the virtual keyword for a method you want to override, you can’t override it.
The virtual keyword is the base class’s way of saying “I give you permission to do something different
with this method,” while the override keyword is the derived class’s way of saying “You gave me
permission, so I’m going to use it and take things in a different direction.”
Revisiting the ‘base’ Keyword
153
You don’t need to override virtual methods, you’re just allowed to. And if a class has more than one virtual
method, you can override some and not others.
Naturally, you can make any number of derived classes that do things their own way:
class RBsSearchEngine : SearchEngine
{
public override string[] Search(string findThis)
{
return new string[] {
"I found something.",
// Thanks EDI
"I found this for you."
// Thanks SIRI
};
}
}
Here’s where it gets interesting. Because our derived classes RBsSearchEngine and GoogleSearch are
inherited from the SearchEngine class, we can use them anywhere a SearchEngine object is required:
SearchEngine searchEngine1 = new SearchEngine(); // The plain old original one.
SearchEngine searchEngine2 = new GoogleSearch();
SearchEngine searchEngine3 = new RBsSearchEngine();
But now we can use that Search method, and depending on the actual type, the various implementations
will be called:
string[] defaultResults = searchEngine1.Search("hello");
string[] googleResults = searchEngine2.Search("hello");
string[] rbsResults = searchEngine3.Search("hello");
We’ll get different results for each of these methods, because each of these three types define the
method differently. The default SearchEngine class provides a default implementation of the method but
the GoogleSearch and RBsSearchEngine “alter the deal” and override the original implementation,
swapping it out for their own. The CLR is smart enough to know which method to use at the right time.
This type of setup, where calling the same method signature results in different methods actually being
executed, is the crux of polymorphism. Polymorphism means “many forms,” and in this situation, we can
see that calling the same method results in different behaviors or forms.
Revisiting the ‘base’ Keyword
Last chapter, when we looked at creating constructors in a derived class, we saw the base keyword, which
lets you access the constructors from the base class. We can also use the base keyword elsewhere to
access non-private things from the base class, including its methods and properties.
While most of the time, you can directly access things in the base class without actually needing to use the
base keyword, when you override a method, you’ll be able to use the base keyword to still get access to
the original implementation of the method.
This is convenient for the cases in which you want to override a method by doing everything the old
method did, and then a little more. For example:
public override string[] Search(string findThis)
{
// Calls the version in the base class.
string[] originalResults = base.Search(findThis);
if(originalResults.Length == 0)
Console.WriteLine("There are no results.");
154
Chapter 23
Polymorphism, Virtual Methods, and Abstract Classes
return originalResults;
}
Abstract Base Classes
Let’s go back to our SearchEngine example. The SearchEngine class indicates that any and every
SearchEngine type object has the ability to search. But looking back at our code, our base SearchEngine
class didn’t do anything intelligent. We provided a dummy implementation that didn’t do anything besides
waste time writing useless code.
In cases like this, instead of providing a dummy implementation, we have an option to provide no
implementation whatsoever. With no implementation or body for a method, the method in question
becomes abstract.
To accomplish this in code, we need to add the keyword abstract to our class, as well as to any methods
that we wish to keep unimplemented in the base class:
public abstract class SearchEngine
{
public abstract string[] Search(string findThis);
}
When a class is abstract, it is allowed to include abstract methods. Abstract methods cannot go inside of
regular, non-abstract classes. When a class is marked abstract, you can’t create any instances of that
class directly. Instead, you need to create an instance of a derived class that isn’t abstract.
So with the code above, you won’t be able to actually create an instance of SearchEngine. Instead, you’d
need to create an instance of one of the derived classes, like GoogleSearch or RBsSearchEngine.
In the code above, you can also see that the method is marked with abstract as well. In this case, the
method doesn’t need a method body, and isn’t even allowed to have one. Instead of providing an
implementation in curly braces below it, you simply put a semicolon at the end of the line.
Abstract classes can also have as many virtual or normal methods as you want.
In a derived class, to provide an implementation for an abstract method, you use the override keyword in
the exact same way as we did with virtual methods.
The ‘new’ Keyword with Methods
There’s one other topic that a lot of people seem to get tangled up with all of this override and virtual
stuff, and that is putting the new keyword on a method.
You are allowed to use the new keyword on a method like this:
public new string[] Search(string findThis)
{
//...
}
When you use the new keyword like this, what you are saying is, “I’m making a brand new method in the
derived class that has absolutely nothing to do with any methods by the same name in the base class.” In
this example, this Search method is completely unrelated to the Search method in the SearchEngine
class.
This is usually a bad idea because it means that you now have two unrelated methods with the same
name and same signature. This name conflict can be resolved, but it is best to just avoid it altogether.
The ‘new’ Keyword with Methods
155
People often get this new keyword confused with overriding virtual and abstract methods for two
reasons. One, it looks similar. The new keyword here gets used in the exact same way as override.
Second, if you forget to put anything in at all (new or override) when you use the same method
signature, you’ll get a compiler warning that tells you to add the new or override keyword. (Or in some
cases, it only mentions the new keyword.) People see the warning and think, “Hmm... I guess I should add
the new keyword to get the warning to go away.”
The bottom line is, you rarely want to use the new keyword. It’s just not what you’re looking for. If you
really are trying to make a new unrelated method, consider using a different name instead.
Try It Out!
Polymorphism Quiz. Answer the following questions to check your understanding. When you’re
done, check your answers against the ones below. If you missed something, go back and review the
section that talks about it.
1.
2.
3.
4.
5.
6.
7.
8.
True/False. Polymorphism allows derived classes to provide different implementations of
the same method.
True/False. The override keyword is used to indicate that a method in a derived class is
providing its own implementation of a method.
True/False. The new keyword is used to indicate that a method in a derived class is
providing its own implementation of a method.
True/False. Abstract methods can be used in a normal (non-abstract) class.
True/False. Normal (non-abstract) methods can be used in an abstract class.
True/False. Derived classes can override methods that were virtual in the base class.
True/False. Derived classes can override methods that were abstract in the base class.
True/False. In a derived class, you can override a method that was neither virtual nor
abstract in the base class.
Answers: (1) True. (2)True. (3) False. (4) False. (5) True. (6) True. (7) True. (8) False.
24
24 Interfaces
In a Nutshell






Interfaces define the things that something makes available to the outside world.
Interfaces in C# define a set of methods, properties, events, and indexers that any class that
uses the interface are required to provide.
Interfaces are built in way that looks like classes or structs: public interface InterfaceName
{ /* Define members in here. */ }
Members of an interface are implicitly public and abstract, and are required to be so.
A class indicates that it implements an interface in the same way it shows it has a base class:
public class AwesomeClass : InterfaceName { /* Implementation Here */ }
You can implement multiple interfaces at the same time. You can also implement interfaces
and declare a base class at the same time.
In this chapter, we’ll cover a feature of C# called an interface, which outlines certain things that something
provides. Interfaces are a fairly straightforward concept compared to some of the things we’ve been
looking at in the last few chapters. We’ll outline what an interface does, how to create one, and how to set
up a class or struct to use one.
What is an Interface?
At a conceptual level, an interface is basically just the boundary it shares with the outside world. Your TV,
for instance, probably has several buttons on it (and if it is really old, maybe even a dial or two). Each of
these things allows the outside world to interact with it. That’s its interface.
Interfaces are everywhere in the real world. They point out to users of the object how it can be used or
controlled and also provides feedback on its current status. A couple of examples are in order here.
Cars (all vehicles in general) have a specific interface that they present to their users (the driver). While
there are some nuances and details that we’ll skip over, the key components are a steering wheel, a gas
pedal, and a brake pedal. A car will also provide certain bits of feedback information to the driver on the
dashboard, including the vehicle’s ground speed and engine speed, as well as its fuel level.
Creating an Interface
157
Likewise, the keys on a piano are an interface that a pianist can access to make music. While pianos come
in different forms (grand pianos, upright pianos, player pianos, electronic keyboards, etc.) they present a
similar interface to the user of the system.
Let’s take a moment and pick out a few key principles that apply to interfaces in the real world, like the car
and keyboard interfaces. We’ll soon see that C# interfaces provide these same key principles.
1.
2.
Interfaces define how outside users make requests to the system. By rotating the steering wheel
on a car clockwise, we’re making a request to turn the vehicle to the right. By pressing Middle C
key on the piano’s keyboard, we’re asking the piano to make a sound at a particular pitch.
Interfaces define the ways in which feedback is presented to the user.
This interface defines how other objects can interact with the car, without prescribing the details of how it
works behind the scenes. For example, in very old cars, this was all done in a purely mechanical way.
Newer cars have things like power steering, and perform some of their feedback to the user digitally. And
consider an electric car, which has a “gas” (acceleration) pedal but drives the vehicle using an electric
motor instead of a gas motor.
You can even go to an amusement park and find bumper cars and speedway/raceway type rides that use
an identical interface to control vehicles. One of the nice things about this shared interface is that as soon
as you understand how to use one object through the interface, you generally understand how to use all
objects that share the same interface.
Interfaces work both ways. When something presents a particular interface to the user, the user assumes
that it’s capable of doing the things the interface promises. When you’ve got a brake pedal in a car, you
assume it will let you stop the vehicle quickly. When the brake pedal doesn’t fulfill the promised interface,
bad things happen. It’s why it is frustrating when an elevator has a “Close Door” button, that doesn’t
actually close the door. The elevator presents an interface that promises the ability to close the door, but
fails to actually do so.
The broader lesson here is that interfaces define a specific set of functionality. Users of the interface can
know what to expect of the object from the interface, without having to know the details of how the object
actually makes it work, and in fact, as long as the interface remains intact, the details on the inside could
be swapped out and the user wouldn’t have to change anything.
C# has a construct called an interface that fills the same role as these interfaces in the real world. It
defines a set of members (specifically methods, properties, events (Chapter 33) and indexers (Chapter 35))
that any type claiming to have the interface must provide.
Creating an Interface
Creating an interface is very similar to creating a class or a struct. As an example, let’s say our program
has a need to write out files (see Chapter 29) but you want to write to lots of different file formats. We can
create a different class to handle the nuances of each file type. But they are all going to be used in the
same way as far as the calling code is concerned. They all have the same interface.
To model this, we can define an interface in C# for file writers. It is worth pointing out that Visual Studio
has a template for creating new interfaces. This can be accessed by going to the Solution Explorer and
right-clicking on the project or folder that you want to add the new interface to, and choosing Add > New
Item, bringing up the Add New Item dialog box. In the list of template categories on the left side, choose
Visual C#, which will show the list of all C# templates in the middle of the dialog. Up near the top, you’ll
see a template called Interface. This will get you set up with a new interface in a manner similar to how
the Class template got us set up with a new class.
158
Chapter 24
Interfaces
Interfaces are structured in a way that looks very much like a class or struct, but instead of the class or
struct keyword, you will use the interface keyword:
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace AwesomeProgram
{
public interface IFileWriter
{
string Extension { get; }
void Write(string filename);
}
}
You can also see that all of the members of our interface have no actual implementation. They look like
the abstract methods that we talked about in the last chapter. But also notice that we didn’t use the
abstract keyword here. Nor do we say that it is public (or private or protected for that matter). All
members of an interface are public and abstract by default, and cannot be altered into anything else. You
cannot add a method body, nor can you use a different accessibility level.
I also want to point out that I started the name of my interface with the capital letter I. It is incredibly
common in the C# world to start names of interfaces with the letter I. People do this because it makes it
easy to pick out what things are interfaces, and what things are classes. Not everyone does this, and you
don’t need to either if you don’t want, but it is fairly widespread so expect to encounter it.
Using Interfaces
Now we can go ahead and create classes that use this interface. For example, we might create a
TextFileWriter class, which knows how to write to a text file. Because we want it to do the things that any
IFileWriter can do, we’ll want to signify that this class implements the IFileWriter interface that we just
defined. This can be done like this:
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
namespace AwesomeProgram
{
public class TextFileWriter : IFileWriter
{
public string Extension
{
get { return ".txt"; }
}
public void Write(string filename)
{
// Do your file writing here...
}
}
}
To make a class use or implement an interface, we use the same notation that we used for deriving from a
base class. We use the colon (‘:’) followed by the name of the interface that we’re using.
Multiple Interfaces and Inheritance
159
If a class implements an interface like this, we are required to include an implementation for all of the
members that the interface specifies. The class can also include a lot more than that, but at a minimum, it
will need to implement the things in the interface. Note, however, that you do not use the override
keyword when implementing an interface method.
In most ways, an interface will work in the same way that a base class does. For instance, we can have an
array of our IFileWriter interface, and put different implementations of IFileWriter in it:
IFileWriter[] fileWriters = new IFileWriter[3];
fileWriters[0] = new TextFileWriter();
fileWriters[1] = new RtfFileWriter();
fileWriters[2] = new DocxFileWriter();
foreach(IFileWriter fileWriter in fileWriters)
fileWriter.Write("path/to/file" + fileWriter.Extension);
Multiple Interfaces and Inheritance
As I mentioned at the end of the Chapter 24, C# doesn’t allow you to inherit from more than one base
class. However, you are allowed to implement more than one interface. Having your type implement
multiple interfaces just means your type will need to include all of the methods for all of the interfaces
that you are trying to implement.
In addition to implementing multiple interfaces, you can also still derive from a base class (and only one).
C# limits you to a single base class because allowing two or more base classes tends to make the
language, the overall structure of your code much more complicated and unintuitive.
To indicate that a class should implement more than one interface, list the interfaces you want after the
class name, separated by commas, in any order. (But base classes must come first.)
public class AnyOldClass : RandomBaseClass, IInterface1, IInterface2
{
// ...
}
Try It Out!
Interfaces Quiz. Answer the following questions to check your understanding. When you’re done,
check your answers against the ones below. If you missed something, go back and review the section
that talks about it.
1.
2.
3.
4.
5.
6.
What keyword is used to define an interface?
What accessibility level are members of an interface?
True/False. A class that implements an interface does not have to provide an
implementation for all of the members of the interface.
True/False. A class that implements an interface is allowed to have other members that
aren’t defined in the interface.
True/False. A class can have more than one base class.
True/False. A class can implement more than one interface.
Answers: (1) interface. (2) Public. (3) False. (4) True. (5) False. (6) True.
25
25 Using Generics
In a Nutshell



Generics in C# are a way to create type-safe classes and structs without needing to commit to
a specific data type at design time.
When you create an instance of a type that uses generics, you must fill in the generic type
parameters in angle brackets (’<’ and ’>’): List<string> listOfStrings = new List<string>();
This chapter also covers some of the details of using the List class, the IEnumerable
interface, and the Dictionary class, all of which use generics.
In this chapter, we’ll take a look at a powerful feature in C# called generics. We’ll start by taking a look at
why generics even exist in the first place, looking at the actual problem they solve. We’ll then look at a few
classes that come with .NET Platform that use generics. These classes are the List and Dictionary classes,
as well as the IEnumerable<T> interface. These types have many uses, and as we make software, we’ll
definitely be putting them and other generic types to good use.
We’ll look at how to make your own generic classes in the next chapter.
The Motivation for Generics
Before we can really discuss generics, we need to discuss why they exist in the first place. So I’m going to
start by having you think about the underlying problem that generics solve.
Let’s say you wanted to make a class to store a list of numbers. While an array could do this, perhaps we
could make a class that wraps an array and creates a new larger one when we run out of space, making it
a “growable” list. (Note that there is a List class that already does this. You should use this and not write
your own. But for now, pretend it doesn’t exist. We’ll revisit List before the end of this chapter.)
Imagine what you’d need to do to make a class to accomplish this task. For example:
public class ListOfNumbers
{
private int[] numbers;
public ListOfNumbers()
The Motivation for Generics
161
{
numbers = new int[0];
}
public void AddNumber(int newNumber)
{
// Add some code in here to make a new array, slightly larger
// than it already was, and add your number in.
}
public int GetNumber(int index)
{
return numbers[index];
}
}
This isn’t complete, but you get the idea. You make the class and use the int type all over the place.
But now let’s say you want a list of strings. Isn’t it a shame that you can’t use your ListOfNumbers class,
and put strings in it?
So what now? Any ideas?
I suppose we could make a very similar class called ListOfStrings, right? It would end up looking basically
the same as our ListOfNumbers class, only with the string type instead of the int type. In fact, we could
go crazy making all sorts of ListOf... classes, one for every type imaginable. But that’s kind of annoying,
because we could have almost a limitless number of those classes. Lists of ints, lists of strings, lists of
lists....
Maybe there’s another way. What if we simply made a list of objects? Remember how object is the base
class of everything?
We could create just a simple List class that uses the object type:
public class List
{
private object[] objects;
public List()
{
objects = new object[0];
}
public void AddObject(object newObject)
{
// Add some code in here to make a new array, slightly larger
// than it already was, and add your object in.
}
public object GetObject(int index)
{
return objects[index];
}
}
Now we can put any type of object we want in it. We only need to create one class (just the List class) for
any type of object we want.
This might seem to work at first glance, but it’s got a few problems as well. For instance, any time you
want to do anything with it, you’ll need to cast stuff to the type you’re working with. If we have a list has
only strings, when we want to call the GetObject method, we’ll need to cast it like this:
string text3 = (string)list.GetObject(3);
162
Chapter 25
Using Generics
Casting takes time to execute, so it slows things down a bit. Worse than that, this is less readable.
There’s a bigger problem here though. Let’s assume we’re sticking strings in our list. We can cast all we
want, but since it’s a list of objects, we could theoretically put anything in the list, not just strings.
There’s nothing preventing us from accidentally putting something else in there. After all, the compiler
can’t tell that there’s anything wrong with saying list.AddObject(new Random()) even though we
intended it to be a list for only strings. We could try to be extremely careful and make sure that we don’t
make that mistake, but there are no guarantees. And even if we are careful ourselves, other people using
the code might make that mistake.
We can never know for sure what type of object we’re pulling out. We’ll always have to check and make
sure that it’s the type we think it is, because it might possibly be something else. Programmers have a
name for this by the way. They say it isn’t type safe. Type safety is where you always know what type of
object you are working with.
Type safety wasn’t a problem in our first approach because we weren’t using object. We were using a
specific type, like string. We knew exactly what we were working with, so we could ensure that we were
using the right thing all the time. There was no need for casting, and no possibility of mistakes.
So we’ve got two bad choices here. Either we make lots of type safe classes of lists, one for each kind of
thing we want to put in it, or we make a single class of plain old objects that isn’t type safe, but doesn’t
require making lots of different versions.
Here is where generics come in to save the day, fixing this tricky dilemma for us.
What are Generics?
Generics are a clever way for you to define special generic types (classes or structs) which save a spot to
place a specific type in later. It is a template of sorts, where some types used within your class are filled in
when the class gets used, not when the class gets defined.
The template is filled in with the actual types to use when the generic type is used, not when the type is
defined. At one time you’ll say, “This time I need a list of ints,” and another time you’ll say, “Now I want a
list of Hamburger objects.”
In short, generics provide a way to define type-safe classes, without having to actually commit to any
particular type when you create the class.
In the next chapter, I’ll show you how to actually create your own generic types, but right now, we’ll start
by taking a look at a few generic types that already exist in the .NET Standard Library that you’ll find very
useful. We’ll start with the List class, which is the “official” version of what we’ve been describing up until
now in this chapter. Then we’ll look at a generic interface (IEnumerable) which is an interface that allows
you to look at all items in a collection, one at a time, and is used by nearly all collection classes. Finally,
we’ll look at the Dictionary class, which is a bit more advanced and shows off a few more of the features
that generics offer.
The List Class
The .NET Platform already includes an implementation of a generic List class, which is a growable,
ordered collection of items (unlike arrays, which can’t be resized). The List class is a great, simple example
that shows us how to use generics in general, but it is also just a useful class to know about, and you’ll use
it frequently as you do C# programming. For both of these reasons, we’ll start our discussion on generics
with the List class.
The List Class
163
This class is called List, so you might think you can create a list like this:
List listOfStrings = new List(); // This doesn't quite work...
But that doesn’t quite work. This is because the List class uses generics, and has a generic type parameter.
Having generic type parameters is what makes a type generic. What it means is that we are going to need
to fill in some types to be used when we attempt to use it. These generic type parameters are filled in by
specifying the types to use inside of angle brackets (‘<‘ and ‘>‘). For a list of strings, you do this:
List<string> listOfStrings = new List<string>();
You now have a list containing strings!
I should point out that the List<T> generic type (as well as IEnumerable<T> and Dictionary<K, V> that
we’ll talk about next) are all in the System.Collections.Generic namespace, so if your file doesn’t have a
using directive at the top of the file for that, you’ll want to add it (Chapter 27). (By default, it is included
though.)
With a generic type like this, the generic type parameter gets filled in across the class. In the above case,
we’ve filled in the string type in many places for this particular instance, and the compiler will be able to
ensure only strings are used to interact with this particular List instance.
As an example, the List class has an Add method, which allows you to put new items in the list:
listOfStrings.Add("Hello World!");
Because we’ve specified that we’re using string for the type parameter, the signature of this Add method
now requires that we give it a string. If we had made it a List<int>, then the Add method would require
that it be given an int. Yet the generic List class was only defined once. We’ve gotten the best of both
worlds: type safety, but without needing to make lots of different class definitions.
Let’s look at a few more examples of using the List class.
If adding items to the end isn’t what you need, you can use the Insert method, which allows you to supply
the index to add the item at, pushing everything else back:
listOfStrings.Insert(0, "text3");
Remember that indices typically start at 0, so the above code inserts at the front of the list.
You can also get an item out of the list using the ElementAt method.
string firstItem = listOfStrings.ElementAt(0);
You can also use the square brackets ([ and ]) with the List class to get and set values at specific indexes,
just like an array. (This is done through an indexer, which we’ll see in Chapter 35.) For example:
string secondItem = listOfStrings[1]; // Get a value.
listOfStrings[0] = "New Value!";
// Set a value.
Also, the RemoveAt method allows you to remove items from the list:
listOfStrings.RemoveAt(2);
While we’re talking about deleting stuff, you can remove everything from the list with the Clear method:
listOfStrings.Clear();
While arrays have the Length property to determine how many items are in the array, the List class has a
Count property instead. (There’s no Length property; the inconsistency is rather unfortunate.)
164
Chapter 25
Using Generics
int itemsInList = listOfStrings.Count;
If we’re using a List, but we want to get a copy of the contents as an array, there’s an easy way to do this.
There’s a ToArray method that will make this conversion for us, copying our generic List instance into an
array of the appropriate type:
List<int> someNumbersInAList = new List<int>();
someNumbersInAList.Add(14);
someNumbersInAList.Add(24);
someNumbersInAList.Add(37);
int[] numbersInArray = someNumbersInAList.ToArray();
We are also able to loop over all of the items in a List, just like with arrays:
List<int> someNumbersInAList = new List<int>();
someNumbersInAList.Add(14);
someNumbersInAList.Add(24);
someNumbersInAList.Add(37);
foreach(int number in someNumbersInAList)
{
// ...
}
Since the List class is generic, you can create an instance of the List class using any other type you want.
And when you do, all of the methods like Add and ElementAt will work only for the type that you are
using, keeping it type safe like we wanted.
Using Collection Initializer Syntax
When we first introduced arrays, we also discussed collection initializer syntax (or simply a collection
initializer) that allowed us to set up an array using simplified syntax:
int[] numbers = new int[] { 1, 2, 3, 4, 5 };
We can do this same thing with the List<T> class as well. The earlier code that added multiple items to a
list could be simplified using collection initializer syntax:
List<int> someNumbersInAList = new List<int>() { 14, 24, 37 };
This trick works on anything that implements the IEnumerable<T> interface (see the next section) and
has an Add method. The C# compiler simply turns that into calls to Add.
The IEnumerable<T> Interface
One of the generic types that you’ll encounter the most is the IEnumerable<T> interface. If you recall
from the chapter on enumerations, the definition of the world “enumerate” means to count off, one-byone, each item in a group or collection. This is essentially what the IEnumerable<T> interface is for. It
allows a collection to give others the ability to look at each item contained in it, one at a time.
Nearly every collection class that you encounter in the .NET world will implement this interface. In that
sense, it serves as the base or lowest level of defining a collection. Both the List class and Dictionary
class that we’ll see next implement it. Even arrays implement IEnumerable<T>.
IEnumerable<T> provides the foundation for all sorts of interesting and important features in C#. In
Chapter 13 when we were talking about arrays and the foreach loop, we saw that if something
implements IEnumerable<T>, it can be used in a foreach loop. And IEnumerable<T> is used as the
foundation for LINQ queries, which we’ll talk about in Chapter 38.
The Dictionary Class
165
All sorts of things implement IEnumerable<T>. If all you care about is the ability to do something with
each item in a collection, you can get away with treating it as simply an IEnumerable<T>:
IEnumerable<int> numbers = new int[3] { 1, 2, 3 };
You’ll see some methods returning IEnumerable<T> if they don’t want you to know the actual concrete
type being used (it could be an array or a List or something else) and if they want you to have the ability
to loop through the items, but not modify the collection.
The Dictionary Class
To wrap up our introduction on using generics, let’s look at another generic class that is slightly more
complex than List: the Dictionary class. This class isn’t quite as versatile as the List class, but you’ll
definitely find uses for it, so it is a good choice to look at.
Let’s start by describing what the Dictionary class actually is. A physical dictionary has a set of words that
you use to look up a definition that belongs to each word. One piece of information is used to look up
another piece of information.
The Dictionary class does this same thing. We use one piece of information (the key) to store and look up
another piece of information (the value). So it is a mapping of key/value pairs. Because the class is generic,
we’re not limited to strings. We can use any type that we want.
For example, let’s look at how you could use a Dictionary to create a phone book. Since Dictionary is a
generic class, we can use it with any type we want. In the case of a phone book, we might use a string
name for the key to look up our own custom-made PhoneNumber class. (Though these are just
examples, and we could use anything else too.)
The Dictionary class has two generic type parameters, one for the key and one for the value. That tells us
we can pick our own type for both keys (we’re not just limited to strings) and also for values. For this
sample, we’ll use string for the keys and PhoneNumber for the values:
Dictionary<string, int> phoneBook = new Dictionary<string, int>();
The Dictionary class allows us to use the indexing operator (‘[‘ and ‘]’) to get or set values in it:
phoneBook["Gates, Bill"] = new PhoneNumber("5550100");
phoneBook["Zuckerberg, Mark"] = new PhoneNumber("5551438");
PhoneNumber billsNumber = phoneBook["Gates, Bill"];
When you go to use a generic type, you fill in values (type names) for each of the generic type arguments
it defines. Most generic types will have one or two generic type parameters that you can fill in, but there
isn’t a limit on the number of generic type arguments that a type can have.
As you can see from the above example, when you fill in the generic type arguments with specific types,
you can mix and match. There’s no requirement for them to all be string or anything of that nature.
There is quite a bit more that the Dictionary class can do, though we won’t get into the details here. Feel
free to explore and see what else is there.
166
Chapter 25
Using Generics
Try It Out!
Generics Quiz. Answer the following questions to check your understanding. When you’re done,
check your answers against the ones below. If you missed something, go back and review the section
that talks about it.
1.
2.
3.
4.
5.
6.
Describe the problem generics address.
How would you create a list of strings, using the generic List class?
How many generic type parameters does the Dictionary class have?
True/False. When a generic class has multiple type parameters, they must all match.
What method is used to add items to a List object?
Name two methods that cause items to be removed from a List.
Answers: (1) Type safety without creating many similar types. (2) List<string> strings = new List<string>(); (3) Two: the key
type and the value type. (4) False. (5) Add. (6) RemoveAt and Clear.
26
26 Making Generic Types
In a Nutshell





You can create your own class that uses generics by placing the generic type in angle brackets
after the class definition: public class PracticeList<T> { /* ... */ }.
Using single capital letters for generic types is common, so T is a common in generic classes.
You can have multiple generic types: public class PracticeDictionary<K, V> { /* ... */ }.
When you include generic type parameters in a type definition, you can use that type name as
a placeholder throughout the type (return types, parameter types, instance variable types,
etc.) to indicate where it will be used.
Placing constraints on a generic type variable restrict the kinds of types that can be used, but
you know more about the generic type and can do more with it.
In the last chapter, we saw how to use a generic type. In this chapter we will look at how to define your
own generic types. Classes, structs, and interfaces can all use generic type parameters.
In this chapter, we’ll outline how to make our own generic PracticeList class. While the List class already
exists (we saw it in the previous chapter) this is a good, simple example that will help us see how to make
generic types. Obviously, you should use the List class and not the one we make here for “real” programs.
Try It Out!
Building Your Own Generic List Class. To help you get a hold of the idea of generics, follow through
the contents of this chapter and build your own PracticeList class that uses generics.
Creating Your Own Generic Types
To add generic type parameters to a type, you add the generic type parameter names to the type’s
definition, as shown below:
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
168
Chapter 26
Making Generic Types
using System.Threading.Tasks;
namespace Generics // Your namespace might be different.
{
public class PracticeList<T>
{
}
}
You can see that this is the same as a normal class, with the exception of having that <T> in there. This is
what allows the class to use generics.
This gives a name (T) to our generic type parameter. This type can then be used across the type for return
types, method parameter types, instance variable types, property types, etc. We’ll see examples of this
very soon, in the upcoming sections.
Generic type names can be any legal identifier, but two patterns are common: using single capital letters
(T, K, V, etc.), and using some simple name, prepended with a T (like TKey and TValue). In places that
need just a single generic parameter, most people tend to just use T.
If you want multiple generic type arguments, you can simply place them in the same place by naming
them inside the angle brackets, separated by commas. For example: <TKey, TValue>.
Using Your Generic Type in Your Class
Now that we’ve created a class with a generic type parameter, we can use this type throughout our class.
For instance, since this is a list, we probably want to store the items of our list somewhere. So we can add
the following as an instance variable:
private T[] items;
We create a private array called items, but you’ll see that the type of that array is T—our generic
parameter type, rather than a “real” type. So whenever someone uses our PracticeList class, they’ll
choose what type to use, and the type they choose will go in here. If they create a new
PracticeList<int>() then this will become an array of ints (private int[] items) for that instance.
We probably also want a constructor that will set up our array to have 0 items in it to start:
public PracticeList()
{
items = new T[0];
}
This is straightforward enough. We create a new array that uses our generic type parameter T with 0
items in it. Yes, having an array with 0 items in it is pretty worthless, but we’ll expand it as we add items.
How about a method that returns the item in the list at a particular index?
public T GetItem(int index)
{
return items[index];
}
In this case, the return type is our generic type parameter T. If we had a PracticeList<int>, then this
would return an int. If it was a PracticeList<double>, it would return a double.
How about a method that adds an item to the list? This is a little more complicated because it needs to
resize the array (technically making a new slightly larger array and then copying the items over to the new
array).
Generic Type Constraints
169
public void Add(T newItem)
{
// Make a new bigger array with room to store the new item.
T[] newItems = new T[items.Length + 1];
// Copy all of the old items over to the new array.
for (int index = 0; index < items.Length; index++)
newItems[index] = items[index];
// Put the new item at the end.
newItems[newItems.Length - 1] = newItem;
// Update our instance variable to hold this new array instead of the old array.
items = newItems;
}
Once again, we are putting our generic type parameter to use. This time, it is used as an input to the
method (we only want to be able to add items of the type that matches the list) and also to create the new
array that we copy everything to.
Our completed class will look something like this:
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace Generics
{
public class PracticeList<T>
{
private T[] items;
public PracticeList()
{
items = new T[0];
}
public T GetItem(int index)
{
return items[index];
}
public void Add(T newItem)
{
T[] newItems = new T[items.Length + 1];
for (int index = 0; index < items.Length; index++)
newItems[index] = items[index];
newItems[newItems.Length - 1] = newItem;
items = newItems;
}
}
}
Generic Type Constraints
So far, our generic type parameters reserve a spot for another type to be used, and literally any type can
be placed in it. This allows people to use anything they want for the generic type in question. It’s usually a
good thing.
170
Chapter 26
Making Generic Types
But it does have one problem: within the generic type, you know next to nothing about the objects that it
is using. The only thing you can guarantee is that it is an object, so the only members you use with
instances of the generic type are ones that belong to object (Equals, ToString, GetHashCode, etc.)
Many scenarios don’t require us to do anything specific with our generic type parameters. For example, a
collection just needs to keep track of it. It doesn’t have to interact with it. In these cases, the above
limitation is not a problem.
But if you do need to be able to do some work with the generic type parameter, you may find that you
want to constrain what actual types can be used for the generic type parameter. If you do this, then you
limit what types can be filled in at the time of use, but you also know much more about your generic type.
As an example, the following shows how you would require that the generic type parameter must be filled
in by something that implements the IComparable interface:
public class PracticeList<T> where T : IComparable
{
//...
}
If we put a generic type constraint in place like this, we will no longer be able to make instances of
PracticeList where the type parameter is filled in with something that doesn’t implement IComparable.
But in exchange, we could now use IComparable methods within the class, because we can be certain
that T is at least that.
Multiple constraints for a single type can be added by separating them with commas, and if you have
multiple generic types, you can specify type constraints for each of them using a new where keyword.
public class PracticeDictionary<K, V> where K : SomeBaseClass, SomeRandomInterface
where V : SomeOtherInterface
{
//...
}
You can also specify a parameterless constructor constraint, which requires that the type used has a
public parameterless constructor. This is done by adding new() as a constraint:
public class PracticeList<T> where T : new()
{
//...
}
With this, we know that our type parameter T has a parameterless constructor and we can invoke it:
T newObject = new T();
Interestingly, you cannot add “parameterful” constructor constraints. (You can’t say where T : new(int), or
anything like that.)
You can also specify that a type must be a value or reference type with the struct or class constraint:
public class PracticeList<T> where T : class
{
//...
}
// Must be a reference type.
Or:
public class PracticeList<T> where T : struct
{
//...
}
// Must be a value type.
Generic Methods
171
You can also indicate that one type parameter must be derived from another type parameter:
public class GenericClass<T, U> where T : U
{
//...
}
Generic Methods
In addition to generic classes, you can have individual methods that use generics as well. These can be a
part of any class or struct, generic or regular. Like with generic classes, this is useful when you want to
have a set of methods that are identical, except for the type that it works on. To do this, you simply list the
generic type parameters after the method name but before the parentheses like this:
public T LoadObject<T>(string fileName) { ... }
Like with classes, you can use as many generic type parameters as you want, apply generic constraints,
and also use the generic type parameters in the method’s parameter list and return type:
public T Convert<T, U>(T item) where T : class
{
// ...
}
To use a generic method, you would do the following:
Person person = LoadObject<Person>("person1.txt");
The Default Operator
C# includes an operator that will produce the default value for any specific type. This looks like this:
int value = default(int);
In short, the default value for a type is as close to 0 in meaning as possible. Specifically:






For all of the integer and floating point types, that’s a value of 0.
For bool, it’s false.
For a char, it is the character whose numeric value is 0—that is the NULL control character.
For an enum, it will be a 0. This applies even if you’ve assigned non-zero values to all of your
enum members.
For a class, including string and arrays, it will be null.
For a struct, each element will take on the default value for its type.
The default operator is useful when you are using generics and need to assign a value to a variable of a
generic type. If you don’t know much about the generic type, then it’s hard to do this. For example, if you
don’t know if something is a value or reference type, then you can’t do something like this:
T returnValue = null;
You can’t assign it null, because it might not be a reference type. Likewise, you can’t assign it a value of 0
or anything else.
In these cases, all you can do is assign it the default value:
T returnValue = default(T);
172
Chapter 26
Making Generic Types
Unfortunately, this sounds more useful than it actually probably is. If you didn’t know enough to assign it
a real value in the first place, then you still won’t know what was created by the default operator. You
aren’t going to be able to do anything with it (short of returning it) once it has been created. There is also
no way in C# to define what the default value should be for your class (or struct) either.
So I bring this up because it is an actual operator and keyword in the C# language, and because it does
have its uses, but it is not the most powerful of tools you’ll find.
In most cases, you will probably want to use the generic type constraints from earlier in this chapter, or
come up with a different strategy for creating actual objects rather than using the default value. (There
are many approaches to this, though this subject is beyond the scope of this book. But if you do a web
search for “creational patterns” you’ll find a number of approaches for getting objects of different types
created that go far beyond C#’s default operator.)
Try It Out!
Making Generics Quiz. Answer the following questions to check your understanding. When you’re
done, check your answers against the ones below. If you missed something, go back and review the
section that talks about it.
1.
2.
3.
4.
How do you indicate that a class has a generic type parameter?
True/False. Generic classes can only have one generic type parameter.
True/False. Generic type constraints limit what can be used for the generic type.
True/False. Constraints let you use the methods of the thing you are constraining to.
Answers: (1) In angle brackets by the class name. (2) False. (3) True. (4) True.
Part
4
Advanced Topics
We’ve covered much of the C# language. Much of the things you might want to do, you can do now,
and much of what you see you’ll be able to understand.
In Part 4, we’ll look topic-by-topic at more advanced features of the C# language, and tackle some
common tasks that you will likely need to do before too long. This includes:














More about namespaces and using directives (Chapter 27).
More about methods (Chapter 28).
Reading from and writing to files (Chapter 29).
Handling errors using exceptions (Chapter 30).
Patterns (Chapter 31).
Delegates (Chapter 32).
Events (Chapter 33).
Overloading operators (Chapter 34) and creating indexers (Chapter 35).
Creating extension methods for existing types (Chapter 36).
Lambda expressions (Chapter 37) and query expressions (Chapter 38).
Multi-threading your application (Chapters 39 and 40).
Dynamic objects (Chapter 41).
Unsafe (unmanaged) code (Chapter 42).
A quick pass at a few other features in C# that are worth knowing a bit about (Chapter 43).
Do not feel like you need to read these chapters in order, or even right away. Feel free to jump
around and read them as you feel you have a need for these topics.
27
27 Namespaces and Using
Directives
In a Nutshell





Namespaces are collections of (usually related) types that are assigned a name collectively.
A type’s fully qualified name is the combination of the namespace name and the type name.
You can always use a type’s fully qualified name.
using directives indicate which namespace to look in for unqualified type names.
Name collisions are when the compiler is aware of two different types with the same name.
They can be resolved by either using fully qualified names or by using an alias.
A using static directive can be added for any static class, allowing you to use the method
without needing the class name: using static System.Console; and later: WriteLine(x);
Back in Chapter 3, when we made our Hello World program, we first saw using directives and the
namespace keyword. At the time, I mentioned that there’s more to understanding these two closely
related concepts, but that it was a discussion for another day. That day has arrived!
In this chapter, we’ll take a look at what a namespace is, and look in-depth at the use of type names,
spread throughout your program. We’ll then look at what using directives actually do. All of this will give
you a much better idea of what is going on with these two important parts of your code.
Namespaces
In a program, it is possible to have two types with the exact same name. Consider this: How many Point
classes do you think exist out there in the world?
Lots. OpenGL libraries (a very common 3D graphics library) will often have one, every UI framework (like
Windows Forms and WPF) has one. The list goes on and on.
You might be thinking, why can’t they all just use the same class? Reuse code, and all of that good
software engineering mumbo-jumbo.
176
Chapter 27
Namespaces and Using Directives
That’s a good thought. In theory. But each of these different libraries needs different things from their
Point class. Some want it in 2D, others want it in 3D. Some want to use floats while others want doubles.
And they each want their Point class to be able to do different things. They’re fundamentally different
types, which just happen to have the same name.
And here’s where namespaces come in to play.
A namespace is simply a grouping of names.
Usually, people will put related code in the same namespace, so you can also think of a namespace as a
module or package for related code. Namespaces are a little like last names. They separate one type with
a certain name from other types with the same name.
Fully Qualified Names
Looking back at that Point class, the Windows Forms Point class is in the System.Drawing namespace.
The WPF version is in the System.Windows namespace. The namespace allows you to distinguish which
of the two you have.
When combined, the namespace name and the class name is called a fully qualified name.
System.Drawing.Point is a fully qualified name, as is System.Windows.Point. There’s no mistaking
which of the two you mean.
Up until now, we haven’t been using fully qualified names. But we could have been. For example, every
place that we’ve used the Math class, we could have used the fully qualified name System.Math. We
could have written it System.Math.PI and System.Math.Sin(1.2).
Using Directives
You can always use a fully qualified name, but in most cases, that’s just too much typing, and it tends to
make your code less readable.
In any particular file, we have the ability to point out a namespace and say, “I’m using the stuff in that
namespace, so if I don’t use a fully qualified name, that’s where you’ll find it.” This is done by putting in a
using directive, which we’ve been doing since the beginning.
At the top of a file, we can list all of the names of the namespaces that we will be frequently using. With a
using directive for System (using System;) we can use the name of anything in the System namespace
without needing to use the fully qualified name. (This particular using directive, along with a few others, is
added to our file for us when we create a new file in Visual Studio.)
This is the reason that we’ve been able to get away with just saying Math.PI all along, instead of
System.Math.PI. Our program already had a using System; statement at the top of the file.
The Error ‘The type or namespace X could not be found’
If you try to use the unqualified name for a type, leaving out the namespace, and you don’t have an
appropriate using directive, you’ll run into the following error:
The type or namespace 'X' could not be found (are you missing a using directive or an assembly reference?)
This lists two possible causes for the problem: missing a using directive or missing an assembly
reference. The first part of that is usually what’s happening. (Missing assembly references are covered in
Chapter 46.) This can easily be solved by simply adding the appropriate using directive. (Or use the fully
qualified name instead.)
The Error ‘The type or namespace X could not be found’
177
There are two ways you can add a missing using directive. First, you could scroll up to the top of the file
and manually type in a using directive for the right namespace. That works, but it has a few small
problems. If you don’t know what namespace you actually need, you have to hunt around the Internet to
figure it out, which takes time and pulls you away from whatever task you were doing.
There’s a shortcut that is much easier.
To illustrate, I’ve gone into my program and deleted the using System; statement in my program, so that
C# doesn’t know what to do with Math anymore.
Once you type something like this:
double pi = Math.PI;
Visual Studio will underline Math in red, because it doesn’t know what to do with it:
When this happens, use the mouse to hover over the underlined word, and two extra pieces of UI will pop
up: a lightbulb icon and a message describing the error with a link to show possible fixes.
By either clicking on the lightbulb or clicking the link to show potential fixes, you’ll get a list of ways this (or
any other) problem could be resolved.
Clicking on using System; will automatically add a using directive for you. (Clicking on the second option
there will automatically change your code to use the fully qualified name.)
This makes it so you don’t have to memorize or hunt down the namespace that things are in because
Visual Studio figures it out for you. You don’t even need to leave what you were working on to do it.
There’s a keyboard shortcut to get this to pop up: Ctrl + . (the period key).
If you are using a type name that is in multiple namespaces, the little drop down box may have multiple
choices in it. If so, you’ll need to be sure to choose the right one. Accidentally choosing the wrong one is a
quick way to a wasted hour and a bad headache.
178
Chapter 27
Namespaces and Using Directives
Name Collisions
On a few rare occasions, you’ll add using directives for two namespaces that both contain a type with the
same name. This is called a name collision because the unqualified name is ambiguous. In this case, even
though you have using directives for an unqualified name, it still can’t figure it out.
One solution to this is to just go back to fully qualified names. That tends to be my preferred solution. It
keeps things clear and unambiguous. But there’s another way to deal with name collisions: aliases.
Using Aliases to Solve Name Collisions
You can also use an alias to solve the problem of a name collision. An alias is a way to create a new name
for an existing type name. You can think of it like a nickname.
To illustrate, imagine you have the following code, which won’t compile because C is a name collision:
using N1;
using N2;
namespace N1
{
public class C { }
}
namespace N2
{
public class C { }
}
namespace NamespaceExample
{
class Program
{
static void Main(string[] args)
{
C c = new C(); // Compiler can't tell if you're using N1.C or N2.C.
}
}
}
You can define an alias with the using keyword (yes, there’s a lot of uses for the using keyword):
using CFromN1 = N1.C;
This line is placed among your other using directives, though most people put namespace aliases after
normal using directives:
using N1;
using N2;
using CFromN1 = N1.C; // The name here is arbitrary. You could even reuse the name 'C'.
namespace N1
{
public class C { }
}
namespace N2
{
public class C { }
}
namespace NamespaceExample
{
class Program
Static Using Directives
179
{
static void Main(string[] args)
{
CFromN1 c = new CFromN1();
}
}
}
When the compiler is resolving unqualified type names, an alias like this will always take precedence over
looking through the things contained in a normal using directive, which helps resolves name collisions.
Avoiding name collisions in the first place is preferred. But failing that, you’ll have to choose between
using fully qualified type names and aliases. Both have pros and cons, and both are commonly used.
Static Using Directives
In Chapter 18, we discussed static classes. For a static class, there is a variation on using directives that
allow you to make the members of that class available without even needing the class name. That is done
by using a static using directive. (Ironically, the syntax reverses the ordering of those words.) As an example
of this, the following code creates a static using directive for both System.Math and System.Console,
which makes it so you can refer to Console.WriteLine as just WriteLine, Math.PI as just PI, etc.:
using static System.Math;
using static System.Console;
namespace StaticUsingDirectives
{
public static class Program
{
public static void Main(string[] args)
{
double x = PI;
WriteLine(Sin(x));
ReadKey();
}
}
}
This is a bit of a double-edged sword. On one hand, it’s more concise, which is a good thing. On the other
hand, when people see a “bare” method reference (like WriteLine) the first assumption is that it lives
elsewhere in the class, which isn’t necessarily true with a static using directive. So use it when it makes
things easier to understand, and avoid it when it makes things harder to understand.
Try It Out!
Namespaces Quiz. Answer the following questions to check your understanding. When you’re done,
check your answers against the ones below. If you missed something, go back and review the section
that talks about it.
1.
2.
3.
4.
5.
6.
True/False. using directives make previously inaccessible code accessible.
True/False. using directives make it so you do not need to use fully qualified names.
True/False. Two types are allowed to have the same name.
True/False. A name collision is when two types have the same name.
Name two ways to resolve a name collision.
What using directive would need to be added so that you can use System.Math’s Abs
(absolute value) method as Abs(x) instead of Math.Abs(x)?
Answers: (1) False. (2) True. (3) True. (4) True. (5) Use fully qualified names or aliases. (6) using static System.Math;
28
28 Methods Revisited
In a Nutshell





Local functions can be defined inside of another method.
Parameters can be made optional by providing a default value for the parameter: public void
DoSomething(int x = 6) { ... }
When calling a method, you can name your parameters: DoSomethingElse(x: 4, z: 2, y:1);
When doing this, you can put the parameters in any order.
By using the params keyword, methods can have a variable number of parameters.
The ref and out keyword can be used on a method parameter to pass the actual variable,
rather than just the contents of the variable, allowing the called method to modify the
contents of variables in the calling method.
We first looked at methods in Chapter 15. We’ll now take a second look at methods and discuss a few
more advanced features, because we’re ready for it now.
Local Functions
We’ve spent a lot of time defining methods that belong directly to a class, struct or other type. As it turns
out, we can actually define a method directly inside of another method.
To clarify, we should probably formalize our terminology here. Generally speaking, a function is a section
of reusable code that optionally takes inputs (parameters) and optionally produces (“returns”) a result.
A method is a specific sub-type of function that is owned by a class or other type. As a member of a class,
the method also has access to class-level instance variables and the other functions or methods defined
in the class. All methods are functions, but not all functions are methods.
Some other languages allow “bare” functions that live outside of any class or type. They’re just defined at
the top level. These functions would not be considered methods because they aren’t members of a class.
C# does not allow these types of functions.
But C# does allow you to define local functions, which are defined inside of another method or function,
rather than directly inside of a class or other type. Here is a simple example:
Optional Parameters
181
public class Program
{
public static void Main(string[] args)
{
int Add(int a, int b)
{
return a + b;
}
Console.WriteLine(Add(1, 1));
Console.WriteLine(Add(2, 2));
Console.WriteLine(Add(3, 7));
}
}
The syntax is mostly the same as you’d expect from a normal method. There are a few caveats worth
mentioning. You can’t specify an accessibility modifier (like public or private). Local functions are actually
more constrained than private. They are only accessible within the scope of a single method. You also
can’t make a local function static.
You can define local functions anywhere within another method. The code above defined it at the start of
the method. You could alternatively define it at the end, or in the middle. No matter where you put it, you
can call the method from anywhere else inside of the method just fine. So this code compiles as well:
void AnyMethod()
{
int Subtract(int x, int y) { return x - y; }
Console.WriteLine(Add(1, 2) + " " + Subtract(6, 4)); // Outputs "3 2"
int Add(int a, int b) { return a + b; }
}
This code has two local functions: a Subtract method at the top and an Add method at the bottom, with
code that calls both in the middle.
But while you can place them anywhere within a method and call them from anywhere else in the
method, you will be doing yourself a big favor if you pick some convention (like always putting local
functions first) rather than just scattering them randomly throughout the parent method.
Local functions can be used to separate out logical chunks of a larger method without making those
chunks accessible to any other part of the class. (If you want the rest of the class to be able to reuse it,
make it a private method in the class instead.) A local function can be invoked repeatedly from inside the
containing method, so it allows for code reuse without making it accessible beyond the method.
Optional Parameters
Let’s say you are making a method that simulates a die roll by picking a random number between 1 and
the number of sides on the die. Suppose your class contained something like this:
private Random random = new Random();
public int RollDie(int sides)
{
return random.Next(sides) + 1;
}
It’s entirely possible that 99% of the time, you’re going to be passing in 6 for the sides parameter. It will be
kind of annoying to always need to say RollDie(6). C# provides a way to specify a default value for a
parameter, making it optional as long as you’re OK with the default. Doing this is pretty easy:
182
Chapter 28
Methods Revisited
public int RollDie(int sides = 6)
{
return random.Next(sides) + 1;
}
With this code, you can now call RollDie() and the value of 6 will be used, or you can still fall back to
putting a value in if you would like:
RollDie();
// Uses the default value of 6.
RollDie(20); // Uses 20 instead of the default.
While you can have as many optional parameters as you want, and can mix and match them with nonoptional parameters, all optional parameters must come at the end after all of the “normal” parameters.
Named Parameters
Occasionally, you’ll go to use a method, but you don’t remember what order the parameters are in. This is
especially true when the method requires several parameters of the same type. As an example, look at
the method below, which takes an input value and clamps it to a particular range, returning the result:
public int Clamp(int value, int min, int max)
{
if(value < min) { return min; } // Bump the value up to the min if too low.
if(value > max) { return max; } // Move the value down to the max if too high.
return value;
// Otherwise, we're good with the original value.
}
When you go to use this method, you might see something that looks like this:
Clamp(20, 50, 100);
When you see this, you always need to do a little digging to figure out what’s going on. Is it going to clamp
the value of 100 to the range 20 to 50? Or clamp the value of 20 to the range 50 to 100?
To avoid this ambiguity, C# allows you to supply names for parameters. This is done by putting the
parameter name, followed by a colon, followed by the value for that parameter:
Clamp(min: 50, max: 100, value: 20);
With named parameters, it is very clear what value belongs to which parameter. Furthermore, as long as
the compiler can figure out where everything goes, this allows you to put the parameters in any order,
making it so that you don’t need to worry about the official ordering, as long as they all have a value.
When you call a method, you can use regular unnamed parameters, or named parameters, but all regular
parameters must come first, and each variable must be assigned to only once.
You can also combine optional parameters with named parameters.
One side effect of this feature is that parameter names are now a part of your program’s public interface.
Changing a parameter’s name, can break code that calls the method using named parameters.
Variable Number of Parameters
Let’s say you want to average some numbers together. We can write a method to average two numbers:
public static double Average(int a, int b)
{
return (a + b) / 2.0;
}
The ‘out’ and ‘ref’ Keywords
183
What if you want to average three numbers? You could write a similar method for that:
public static double Average(int a, int b, int c)
{
return (a + b + c) / 3.0;
}
What if you wanted 5? Or 10? You could keep adding more and more methods, but it gets unwieldy fast.
C# provides a way to supply a variable number of parameters to a method, by using the params keyword
with an array variable:
public static double Average(params int[] numbers)
{
double total = 0;
foreach (int number in numbers)
total += number;
return total / numbers.Length;
}
To the outside world, the params keyword makes it look like you can supply any number of parameters:
Average(2, 3);
Average(2, 5, 8);
Average(41, 49, 29, 2, -7, 18);
However, behind the scenes, the C# compiler will turn these into an array to use in the method call.
It is also worth pointing out that you can directly pass in an array to this type of method:
int[] numbers = new int[5] { 5, 4, 3, 2, 1 };
Average(numbers);
You can combine a params argument with other parameters, but the params argument must be the last
one, and there can be only one of them.
The ‘out’ and ‘ref’ Keywords
Back in Chapter 16, we talked about value and reference semantics. We looked at how whenever you pass
something into a method as a parameter, the contents of the variable are copied. With value types, this
meant we got a complete copy of the original data. For reference types, we got a copy of the reference,
which was still pointing to the same object in the heap.
Methods have the option of handing off the actual variable, rather than copying its contents, by using the
ref or out keyword. Doing this means that the called method is working with the exact same variable that
existed in the calling method.
This sort of creates the illusion that value types have been turned into reference types, and sort of takes
reference types to the next level, where it feels like you’ve got a reference to a reference.
To do this, you add either the ref keyword or the out keyword to a particular parameter.
public void MessWithVariables(out int x, ref int y)
{
x = 3;
y = 17;
}
We can then call this code like this:
184
Chapter 28
Methods Revisited
int banana = 2;
int turkey = 5;
MessWithVariables(out banana, ref turkey);
At the end of this code, the banana variable will contain a value of 3, and the turkey variable will contain
a value of 17.
If you look at this code, you’ll see that you need to put out or ref when you call the method as well.
There’s a good reason for this. Handing off the actual variables from one method to another is a risky
game to play. By requiring these keywords in both the called method and the calling method, we can
ensure that the variables were handed off intentionally.
There is a small difference between using ref and out, and that difference has to do with whether the
calling method or the called method is responsible for initializing the variable. With the ref keyword, the
calling method needs to initialize the variable before the method call. This allows the called method to
assume that it is already initialized. These are sometimes called reference parameters.
With the out keyword, the compiler ensures that the called method initializes the variable before
returning from the method. These are sometimes called output parameters.
Using reference or output parameters does a couple of things. Because we’re passing the actual variable,
instead of just the contents of the variable, value types won’t need to be completely copied when passed
into a method. This can speed things up because it could save us from copying a lot of value types.
You can also use this to get back multiple values from a method. You can technically only return one value
from a method, but additional values can be returned as an output parameter instead. (More on this later
in this chapter.)
Sending the actual variable to a method, instead of just the contents of the variable is a dangerous thing
to do. We’re giving another method control over our variables, and if used carelessly, it causes a lot of
trouble. Use it sparingly and wisely.
Inline Declarations for Output Parameters
It is possible to simultaneously declare a variable for an output parameter in the same location as the
method invocation. To illustrate, look at the double.TryParse method, which illustrates a pretty common
use of an output parameter:
if (double.TryParse("3.14", out double newValue))
return newValue * newValue;
else return double.NaN;
The double.TryParse method takes a string and parses it (analyzes the text to determine the numeric
value that matches the text representation), but it does so conditionally. It returns a bool that indicates
whether it was successful or not. If it can’t parse it (for example, if the string contained “Jabba the Hutt”
instead of “3.14”) then rather than blowing up on us, it will simply return false, rather than true. Since the
return value is used to indicate whether it was successful or not, the method does not return the parsed
value. Instead, it gives that back via the output parameter.
Our earlier example showed declaring the variable on a separate line. While that’s always an option, this
last example shows that an output parameter can also be defined inline, which tends to produce cleaner
code. (This is a C# 7 feature, so if you’re using an older version, you will need to declare the variable on an
earlier line.)
Ref Return Types and Ref Local Variables
In addition to using ref on parameters, ref can also be used for local variables and return values.
The ‘out’ and ‘ref’ Keywords
185
Consider a scenario from the game development world. While there are many ways you can structure
your game, let’s say you have a game where the state of the game is represented as a collection of many
starships. Suppose you declare a Ship struct that looks like this (being a struct instead of a class is
important here):
public struct Ship
{
public double X { get; set; }
public double Y { get; set; }
public int PlayerIndex { get; set; }
}
And then suppose you’ve defined your game’s current state along these lines:
public class GameState
{
private Ship[] ships;
public Ship GetShip(int index) // BROKEN!
{
return ships[index];
}
}
(Please note that I’m not saying this is the best way to organize your game’s code. I’m not even suggesting
it’s a good way. But it does serve to illustrate the point.)
What problem do you get when you attempt to use a value returned from that GetShip method to adjust
a ship’s position?
The issue goes back to the difference between value types and reference types. Note that Ship is a struct,
which means it has value semantics. When we call GetShip, a Ship instance is returned, but it’s a copy
(because of the value semantics) of the intended one.
If somebody tries to modify the ship using code like this, it will fail:
Ship ship = gameState.GetShip(2);
ship.X += 0.1;
Instead of modifying the one in the game state’s ships array, we are modifying a copy.
An easy way around this is to use a ref return value, which passes the result back by reference, instead of
by value, in a manner similar to what we saw with a ref parameter or an out parameter:
public class GameState
{
private Ship[] ships;
public ref Ship GetShip(int index)
{
return ref ships[index];
}
}
This can then be called like so:
ref Ship ship = ref gameState.GetShip(2);
ship.X += 0.1;
Like before, you will need the ref keyword on both sides of the method (where we return the value, as
well as where we store the value when it comes back). This helps establish a clear understanding that you
are messing with somebody else’s data—that you have a reference to something that will modify
186
Chapter 28
Methods Revisited
somebody else’s data. If that is the intent, you’re fine. If it’s not the intent, the ref keyword should make
that obvious very quickly.
You’ll also note that the local variable that stores the returned value must also be marked with ref as well.
So ref return types and ref local variables go hand-in-hand. You’ll see them together frequently.
There are other options here. We could have made Ship a class instead of a struct. This would have
immediately given us reference semantics in all cases. That would have probably made this particular
sample simpler.
We also could have simply asked the GameState class to do the ship position change through a method
instead. Something like void GameState.MoveShip(int shipIndex, double xAmount, double
yAmount).
Like usual, choose the one that produces the most understandable and maintainable code.
Try It Out!
Methods Revisited Quiz. Answer the following questions to check your understanding. When you’re
done, check your answers against the ones below. If you missed something, go back and review the
section that talks about it.
For questions 1-3, consider the following code: void DoSomething(int x, int y = 3, int z = 4) { ... }
1.
2.
3.
4.
5.
6.
Which parameters are optional?
What values do x, y, and z have if called with DoSomething(1, 2);
What values do x, y, and z have if called with the following: DoSomething(x : 2, z : 9);
True/False. Optional parameters must be after all required parameters.
True/False. A parameter that has the params keyword must be the last parameter.
Given the method void DoSomething(int x, params int[] numbers) { ... } which of the
following are allowed?
a.
DoSomething();
b.
DoSomething(1);
c.
DoSomething(1, 2, 3, 4, 5);
d.
DoSomething(1, new int[] { 2, 3, 4, 5 });
7. True/False. Parameters that are marked with out result in handing off the actual variable
passed in, instead of just copying the contents of the variable.
8. True/False. Parameters that are marked with ref result in handing off the actual variable
passed in, instead of just copying the contents of the variable.
9. True/False. Parameters that are marked with out must be initialized inside the method.
10. True/False. Parameters that are marked with ref must be initialized inside the method.
Answers: (1) y and z. (2) x=1,y=2,z=4. (3) x=2,y=3,z=9. (4) True. (5) True. (6) b, c, d. (7) True. (8) True. (9)True. (10) False.
Returning Multiple Values
Generally speaking, methods should only return one value. Returning multiple values is a sign that the
method might be doing too much and should be broken down into multiple different methods. But there
are certain scenarios where multiple return values are legitimately needed.
One example of this is wanting to be able to compute both the quotient and remainder of an integer
division operation. Perhaps you remember doing this in school, where 26 / 5 is 5 remainder 1. In this case,
we can compute both the quotient (plain old 26 / 5, which results in 5 because of integer division) and the
Returning Multiple Values
187
remainder using the % operator (26 % 5 is 1). We could make a method to compute both the quotient and
the remainder as a pair.
We’re going to use the problem above—the simultaneously computing the division and remainder
together—as a sample for the rest of this section. But having said that, the Math class actually has a static
method (Math.DivRem) that does this very thing. In other words, you don’t need to write your own. You
can reuse the existing method.
Output Parameters
One way to return multiple values is with output parameters, as discussed in the previous section. The
syntax with ref and out is kind of ugly, but it does give you an easy way to pass values back out using a
parameter to do so.
public static int DivRem(int dividend, int divisor, out int remainder)
{
remainder = dividend % divisor;
return dividend / divisor;
}
This is used like this:
int division = DivRem(26, 5, out int remainder);
Console.WriteLine(division + " remainder " + remainder);
Custom Return Type
Traditionally, return values are meant for values computed by the method, while parameters are meant
for values provided to the method. So using a parameter as output goes against the conceptual pattern.
As an alternative to using output parameters, you can take multiple values and package them up into a
single type (a class or struct, though this is a good fit for a struct). Perhaps something like this:
public struct DivRemResults
{
public int Division { get; }
public int Remainder { get; }
public DivRemResults(int division, int remainder) { Division = division; Remainder = remainder; }
}
With a custom return type, our DivRem method might look like this:
public static DivRemResults DivRem(int divisor, int dividend)
{
return new DivRemResults(divisor / dividend, divisor % dividend);
}
Our calling code would then look more like this:
DivRemResults results = DivRem(26, 5);
Console.WriteLine(results.Division + " remainder " + results.Remainder);
This version uses return values and parameters more traditionally, but it does require creating a unique
type (like DivRemResults) any time you do this.
Using Tuples
A variation on the last option is to use a class (or struct) that can use generics to store these one-off
classes. The .NET Platform has one such class (technically a group of similarly named classes) that can do
this for you: the Tuple classes.
188
Chapter 28
Methods Revisited
There is not just a single Tuple class, but a whole family of them. The Tuple classes are simple generic
containers for a specific number of items. There is a 2-item tuple (Tuple<T1, T2>) a 3-item tuple
(Tuple<T1, T2, T3>) and so on, up to 7 items.
Since these are generic, you can substitute in whatever type you need. So rather than creating the
DivRemResults class in the last section, we could have used a Tuple<int, int>:
public static Tuple<int, int> DivRem(int divisor, int dividend)
{
return new Tuple<int, int>(divisor / dividend, divisor % dividend);
}
Which could be called like this:
Tuple<int, int> results = DivRem(26, 5);
Console.WriteLine(results.Item1 + " remainder " + results.Item2);
In theory, you could almost get away with using Tuple for pretty much all of your data in your program. In
practice, that’s a bad idea. Aside from the usefulness of being able to add your own methods, properties,
etc., the naming going on inside the Tuple classes is awful. The type name, Tuple, tells you nothing about
what kind of data it stores, and the properties for each individual item in the tuple (Item1, Item2, etc.) is
worse. Giving names to your data is extremely beneficial, and Tuple doesn’t allow that.
There are places where it will make more sense to make custom classes with good names, while other
places might be better off just using Tuple. There is a place for both of these options.
ValueTuples and Language-Level Support of Tuples
Our final option for returning multiple values is a new feature of C# 7. Some changes were made to the
language that give new syntax for creating and working with tuples. Of greatest importance is that the
language now (mostly) has a way to directly return multiple values, or at least the appearance of that.
Behind the scenes, a tuple is being created and returned instead, like we saw in the last section.
This language-level support for tuples was not applied to the existing Tuple classes, but to new
ValueTuple structs instead. The language designers felt that it would be better to do multiple return
values with a value type (hence the ValueTuple structs) instead of with a reference type (like the older
Tuple classes).
Overall, ValueTuple functions the same as Tuple, just as a struct (value type) instead of a class (reference
type). There are multiple versions with a different number of arguments, all of which are generic.
There is a rather significant catch to the ValueTuple structs. Your project does not reference the thing
that contains ValueTuple by default. It has to be added before anything in this section will work.
Adding references to other packages like this is discussed in Chapter 46. In fact, that chapter specifically
uses this ValueTuple package as its example. You should feel free to jump ahead and read Chapter 46 (or
anything in Part 5, for that matter) whenever you want, including right now, so that you can get your
hands on ValueTuple to explore and use it in your projects in the future. Specifically, you will want to read
the section called “NuGet Packages”, which will walk you through adding the System.ValueTuple package.
The simplest way to use the ValueTuple structs is just like the Tuple classes that we saw in the last
section:
public static ValueTuple<int, int> DivRem(int divisor, int dividend)
{
return new ValueTuple<int, int>(divisor / dividend, divisor % dividend);
}
Which could be called like this:
Returning Multiple Values
189
ValueTuple<int, int> results = DivRem(26, 5);
Console.WriteLine(results.Item1 + " remainder " + results.Item2);
But what’s really cool is the language level support for creating and working with ValueTuples. Consider
this code:
public static (int division, int remainder) DivRem(int divisor, int dividend)
{
return (divisor / dividend, divisor % dividend);
}
Which can be invoked like this:
(int division, int remainder) = DivRem(26, 5);
Console.WriteLine(division + " remainder " + remainder);
This code is functionally the same as our earlier code, just with better, cleaner syntax. In the DivRem
method, we can get a ValueTuple<int, int> created by simply placing our two int values in parentheses.
Similarly, our return type is specified as (int division, int remainder), which is also a shorthand way of
indicating we return a ValueTuple<int, int>. Perhaps more importantly, this actually allows us to override
the names of the tuple members from Item1 and Item2 to division and remainder.
You can also see on the other side that even though it is technically a ValueTuple being returned, we can
immediately unpack it into separate, named variables: (int division, int remainder). These names don’t
have to match the names provided by the method definition, but probably frequently will.
You can omit the names of the return types if you want:
public static (int, int) DivRem(string value)
If you do this, you can still unpack it to named local variables after returning, like we did before, though in
this case, if you store the results in a ValueTuple directly, the names of the items will be Item1 and
Item2.
Furthermore, you’re allowed to use the nice language-level syntax for working with ValueTuple on one
side of the return, but keep it as a ValueTuple on the other side as shown below:
ValueTuple<int, int> results = DivRem(26, 5);
Console.WriteLine(results.Item1 + " remainder " + results.Item2);
Same the other way around. If you define DivRem like this:
public static ValueTuple<int, int> DivRem(int divisor, int dividend)
You can still use this code:
(int division, int remainder) = DivRem(26, 5);
Console.WriteLine(division + " remainder " + remainder);
Using ValueTuples is a convenient and powerful way to return multiple values from a method. In most
cases, this will probably be the best option if you find yourself truly needing to return multiple values.
ValueTuples do not eliminate the usefulness of the previous options we discussed. It’s a great option, but
every option listed earlier will be easier to read or better in different scenarios. Pick the one that least to
the most understandable and most maintainable code.
And always remember: needing to return multiple values is frequently an indication that a method is
doing too much. Before plotting how to return multiple values, first make sure that it’s the right move.
29
29 Reading and Writing Files
In a Nutshell





The File class is a key part to any file I/O.
You can write out data to a file all in one go, using File.WriteAllText or File.WriteAllLines.
You can read the full contents of a file all at once using File.ReadAllText or File.ReadAllLines.
You can read/write a file a little at a time by getting a FileStream object from File.OpenRead
or OpenWrite, and wrap that in a TextReader/TextWriter or BinaryReader/BinaryWriter.
You need to ensure that unmanaged resources are cleaned up when using FileStreams.
Working with files is a very common task in programming. Reading from and writing to files is often called
file input and file output respectively, or simply file I/O.
There is a collection of types that will make it very easy to work with files. In this chapter, we’ll look at how
to write to a file or read from a file all in one step. We’ll then look at how to work with files doing a little
reading or writing at a time, in both text and binary formats.
The File Class
Writing the File
There are two simple ways to write a bunch of stuff to a file all at once. These ways are defined in the File
class, which is going to be the starting point for all of our file I/O.
To write a bunch of text to a file, we can use the File class’s static WriteAllText method:
string informationToWrite = "Hello persistent file storage world!";
File.WriteAllText("C:/Place/Full/Path/Here.txt", informationToWrite); // Can also be a relative path.
Note that the File class is in the System.IO namespace which isn’t included by default, so you’ll have to
add a using directive (using System.IO;) in as we described back in Chapter 27.
Alternatively, we can take an array of strings and write them out to a file, with each item in the array
placed on its own line in the file, using the WriteAllLines method:
The File Class
191
string[] arrayOfInformation = new string[2];
arrayOfInformation[0] = "This is line 1";
arrayOfInformation[1] = "This is line 2";
File.WriteAllLines("C:/Place/Full/Path/Here2.txt", arrayOfInformation); // Can also be a relative path.
Both of these require the path (absolute or relative) to the file to write to, along with the text to write out.
Reading the File
Reading a file back in is just as easy:
string fileContents = File.ReadAllText("C:/Place/Full/Path/Here.txt");
string[] fileContentsByLine = File.ReadAllLines("C:/Place/Full/Path/Here2.txt");
This does the same thing, but in reverse. In the first part, the entire contents of the file are “slurped” into
the fileContents variable. On the second line, the whole thing is pulled in as an array of strings, where
each line in the file is a different item in the string array.
Assembling and Parsing File Contents
Using File.ReadAllText or File.ReadAllLines is simple and easy to work with, but your work doesn’t
usually end there. A string, or an array of strings is often not the final format of our information.
As an example, let’s say you have a file that contains high scores for a game. Our file might look like the
following, with a header line and each entry on a separate line below that, with values separated by
commas. (This format is called a CSV file, and can actually be created in Excel, as well as a text editor.)
Name,Score
Arwen,2778
Gimli,140
Bilbo,129
Aragorn,88
Let’s say we have a matching HighScore class that we created with a Name property and a Score
property:
public class HighScore
{
public string Name { get; set; }
public int Score { get; set; }
}
To go from our program’s internal representation (e.g., HighScore[ ]) to a file, we might this:
public void SaveHighScores(HighScore[] highScores)
{
string allHighScoresText = "Name,Score\n";
foreach(HighScore score in highScores)
allHighScoresText += $"{score.Name},{score.Score}\n"; // String interpolation again.
File.WriteAllText("highscores.csv", allHighScoresText);
}
To read the file back in and reassemble your high scores list, you could read in the entire text of the file,
then parse (the process of breaking something into smaller, more meaningful components) the text and
turn it back into our list of high scores. The following code reads in the high scores file we just created and
turns it back into a high scores list:
public HighScore[] LoadHighScores(string fileName)
{
string[] highScoresText = File.ReadAllLines(fileName);
192
Chapter 29
Reading and Writing Files
HighScore[] highScores = new HighScore[highScoresText.Length];
for (int index = 1; index < highScoresText.Length; index++)
{
string[] tokens = highScoresText[index].Split(',');
string name = tokens[0];
int score = Convert.ToInt32(tokens[1]);
highScores[index] =new HighScore() { Name = name, Score = score };
}
return highScores;
}
The Split method is going to be our friend when we read stuff from a file. This breaks one string into
smaller strings, splitting it where it runs into a particular character (the commas in this case).
The Convert class has lots of different methods to convert to different types, so pick the ones you need
to convert the incoming strings into the types that you need.
Text-Based Files
We can also work with text files without doing it all at once, giving us more control over the process.
Writing the File
We’ll start with the File class again, but this time, instead of writing out the entire file all at once, we’ll just
open the file for writing. In doing so, we’ll end up with a FileStream object, which we’ll wrap in a
TextWriter object to simplify the process, and use that to write out stuff as needed.
FileStream fileStream = File.OpenWrite("C:/Place/Full/Path/Here3.txt");
StreamWriter writer = new StreamWriter(fileStream);
writer.Write(3);
writer.Write("Hello");
writer.Close();
The OpenWrite method returns a FileStream object. We could use FileStream directly, but it is very low
level, and requiring work with individual bytes. Since we don’t want to spend our lives pushing bytes
around, we wrap the FileStream object in a StreamWriter object, which works at a higher level. We can
then ask it to directly write strings, ints, and other things with the Write method.
When we’re done writing, we call the Close method to release any connections we have to the file.
Reading the File
Reading a file this way is actually more troublesome than writing it was. The problem is that when we
write stuff out to a file, there’s no real way to know how it was structured. We lose our context when we
write to a file this way. We could fall back to File.ReadAllLines (and that’s preferred in many cases). But it
is worth showing how you could use StreamReader to mirror how we write our file out in the first place.
FileStream fileStream = File.OpenRead("C:/Place/Full/Path/Here3.txt");
StreamReader reader = new StreamReader(fileStream);
char nextCharacter = (char)reader.Read();
// Read a single character at a time.
char[] bufferToPutStuffIn = new char[2];
// Read multiple characters at a time.
reader.Read(bufferToPutStuffIn, 0, 2);
string whatWasReadIn = new string(bufferToPutStuffIn);
string restOfLine = reader.ReadLine();
// Read a full line at a time.
Binary Files
193
reader.Close();
We start by opening the file, this time with the OpenRead method. We then wrap the FileStream object
in a TextReader, and then we’re ready to start reading.
To read in a single character, you can use the Read() method. It returns an int, so you’ll need to cast it to a
char. (-1 is returned if the end of the file has been reached.)
There is another overload of the Read method that allows you to read in many characters at once, if you
know how many you want to read in. You can see from the above example that to do this, you need to
create a char array to store the characters in. (The 0 and 2 that are passed in to Read are the spot in the
buffer to start writing at and the number of characters to read total.) This char array can then be turned
into a string, as the example shows.
The TextReader class has a few other methods that you may find valuable, so if you really want to go this
route, take a look at what other methods it has.
When we’re done, we call the Close method to make sure that we are no longer connected to the file.
Binary Files
Instead of writing a text-based file, another choice is to write a binary file. In binary format, you won’t be
able to open the file in a simple text editor and make sense of it.
Binary files have a couple of advantages. First, binary files usually take up less space than text-based files.
Second, the data is “encrypted” to some extent. (I’m using that term very loosely here, since this isn’t true
encryption.) Since it isn’t text, people can’t just open it and read it. It sort of protects your data.
Writing the File
You’ll see that this is very similar to the text-based version in the previous section. The code to write to a
binary file is the same, with the exception of using a BinaryWriter instead of a StreamWriter:
FileStream fileStream = File.OpenWrite("C:/Place/Full/Path/Here4.txt");
BinaryWriter writer = new BinaryWriter(fileStream);
writer.Write(3);
writer.Write("Hello");
writer.Close();
Like before, we open a FileStream that’s connected to the file we want with OpenWrite. This time
though, we wrap it in a BinaryWriter instead of a TextWriter.
We then do as many Write calls as needed. When done, we call Close to release our file connection.
Reading the File
Reading a binary file is actually quite a bit simpler to work with than the text-based version was.
FileStream fileStream = File.OpenRead("C:/Place/Full/Path/Here4.txt");
BinaryReader reader = new BinaryReader(fileStream);
int number = reader.ReadInt32();
string text = reader.ReadString();
reader.Close();
In this scenario, data is read in through BinaryReader’s various ReadBlah methods (ReadInt32,
ReadString, etc.). When done, we call Close to release our hold on the file.
30
30 Error Handling and
Exceptions
In a Nutshell








Exceptions are C#’s built-in error handling mechanism.
Exceptions package up information about a problem in an object. They are “thrown” from the
method that discovered the problem. The exception goes up the call stack until it is handled
by something or until it reaches the top of the call stack unhandled, and kills the program.
If you know you are running code that could potentially cause a problem, you wrap it in a trycatch block, putting the type of exception in the parentheses of the catch block: try { /*
exception thrown here? */ } catch(Exception e) { /* handle problem here */}
You can catch Exception or any type derived from Exception (e.g., FormatException).
Catching a more specific type will catch only that type of error, letting others be handled
elsewhere.
You can string together multiple catch blocks, going from most specific to least specific types
of exceptions: try { } catch(FormatException e) { /* handle format errors here */ }
catch(Exception e) { /* handle all other errors here */ }
You can create your own types of exceptions by deriving from the Exception class.
If your code discovers a problem, you can start an exception with the throw keyword: throw
new Exception("An error has occurred.");
Exception filters allow you to catch an exception only if certain conditions are met, letting
others go on to another catch clause: catch(Exception e) if(e.Message == "Message")
We’ve spent the entirety of this book so far pretending that everything in our program went according to
plan. But that never happens in real life. Sometimes a method is running, trying to do its job, when it
discovers that something has gone terribly wrong.
As an example, think back to Chapter 15, where we looked at a method that was designed to get a
number from the user that was between 1 and 10. That initial version of the code looked like this:
How Exception Handling Works
195
static int GetNumberFromUser()
{
int usersNumber = 0;
while(usersNumber < 1 || usersNumber > 10)
{
Console.Write("Enter a number between 1 and 10: ");
string usersResponse = Console.ReadLine();
usersNumber = Convert.ToInt32(usersResponse);
}
return usersNumber;
}
This code works perfectly, as long as the user enters a number. But what if they enter “asdf” instead?
When we get to the point where we call Convert.ToInt32, things fall apart. We’re asking it to do
something that is an exception to the normal circumstances: turn text that has nothing to do with
numbers into a number. Not surprisingly, Convert.ToInt32 is going to fail at this task. If we don’t find a
way to successfully handle this error, it will ultimately bring down our whole application.
C# provides a powerful way to trap or “catch” these errors and recover from them gracefully. C# uses an
approach called exception handling. It is similar to many other languages, like C++, Java, and VB.NET.
How Exception Handling Works
When an error occurs, the code that discovered the problem will package up all of the information it
knows about the problem into a special object called an exception. These exceptions are either an instance
of the Exception class or a class derived from the Exception class.
Since the method that discovered the error does not know how to recover from it (otherwise it would just
handle it itself, without creating an exception), it takes this Exception object and throws it up to the
method that called it, hoping it knows how to resolve the issue. When this happens, it is important to
know that once an exception is thrown, the rest of the method will not get executed.
Hopefully, the calling method will know what to do in the event that this particular problem occurs. If it
does, it will catch the exception, and find a way to recover from the problem. If this method isn’t able to
provide a solution, the exception bubbles up to the next method, farther and farther up the call stack.
If the exception makes it all the way back to the top of the call stack (usually the Main method) without
catching and handling the exception, the program will crash and terminate. This is called an unhandled
exception. Clearly, you want to avoid unhandled exceptions whenever possible, but from a practical
standpoint, some inevitably get missed.
In debug mode, Visual Studio intercepts these unhandled exceptions at the last minute. This is actually
really convenient. Worst case scenario, this works a little like an autopsy. You’ll be able to dig around and
see what the actual conditions were when the problem occurred, so that you can know what changes you
need to make to fix the problem. In some cases, you can even make the needed change and tell the
program to continue running, which might be successful after your fix. This is like being able to change a
flat tire on a car as it is still racing down the highway. (Debugging is the focus of Chapter 48.)
Through the rest of this chapter, we’ll look at what C# code you need to catch exceptions, handle different
types of exceptions in different ways, and how to throw your own exceptions if you discover a problem
yourself. Lastly, we’ll discuss the finally keyword, and how it ties in to exception handling.
196
Chapter 30
Error Handling and Exceptions
Catching Exceptions
Before we can catch exceptions, we have to be aware that they could occur. In other words, we need to
know that what we’re doing may cause problems that we could handle and recover from.
To catch exceptions, we take potentially dangerous code and place it in a try block. After the try block, we
place a catch block that identifies the exceptions we can handle, and how to resolve them.
Going back to our earlier example of getting a number between 1 and 10 from the user, there are two
potential problems that could arise. First, as we already discussed, the user could type in text instead.
Second, they could type in a number that is so large that it can’t actually be converted into something that
fits into an int.
To catch these errors, we can add in code that looks like this:
static int GetNumberFromUser()
{
int usersNumber = 0;
while(usersNumber < 1 || usersNumber > 10)
{
try
{
Console.Write("Enter a number between 1 and 10: ");
string usersResponse = Console.ReadLine();
usersNumber = Convert.ToInt32(usersResponse);
}
catch(Exception e)
{
Console.WriteLine("That is not a valid number. Try again.");
}
}
return usersNumber;
}
Inside of the try block, we put the potentially dangerous code. After the try block is a catch block that
handles the exception if it goes wrong. This particular catch block will catch any and all exceptions that
occur, because the type specified in the catch block is Exception, which serves as the base class of all
exception types. In general, any code in this book can go in either the try or the catch block.
In this particular case, we “handle” the exception by simply telling the user that what they entered didn’t
make any sense. Because the usersNumber variable wasn’t ever updated, it will still be 0, and the while
loop will cause the flow of execution to loop back around and ask for another number.
In the code above, the information about the exception is captured in a variable named e that has the
type Exception. You can use that variable inside of the catch block as needed. It’s just a normal variable.
If you don’t actually use the variable in the catch block, you can actually leave the variable name off:
try
{
//...
}
catch(Exception) // this Exception variable has no name
{
//...
}
Without a name, you can’t actually use the exception variable, but it leaves the name available for other
things, and you won’t get the compiler warning that says, “The variable e is declared but never used.”
Handling Different Exceptions in Different Ways
197
Handling Different Exceptions in Different Ways
I mentioned earlier that exceptions are packaged up into an instance of the Exception class (or a derived
class). Different types of exceptions are represented with different classes derived from Exception (and
nearly always include Exception in the name). With our earlier examples of catch blocks, we catch every
type of exception without fail. That catch block will catch them all, regardless of what the actual error was.
(I like to call this “Pokémon exception handling.”) In most cases, it is better to handle different types of
errors in different ways—you will nearly always have different courses of action to different types of
errors. For example, the following code handles three different types of errors in three different ways:
try
{
int number = Convert.ToInt32(userInput);
}
catch (FormatException e)
{
Console.WriteLine("You must enter a number.");
}
catch (OverflowException e)
{
Console.WriteLine("Enter a smaller number.");
}
catch (Exception e)
{
Console.WriteLine("An unknown error occured.");
}
When you do this, only one of the catch blocks will actually be executed. Once the exception is handled, it
will skip the rest of the catch blocks. So if a FormatException is thrown, it enters the first catch block
and run the code there to fix the problem, but the catch(Exception e) block will not get executed, even
though the exception was technically of the type Exception. It was already handled.
Doing this makes it so you can handle different types of exceptions in different ways. You just need to be
sure to put the more specific type—the derived type—before the more general, base type. If your first
block was the catch(Exception e) block, it would catch everything, and nothing would ever get into the
FormatException or OverflowException blocks. So ordering is important.
You don’t need to catch all exceptions that might come up. You can build your catch blocks in a way that
some are handled while others are allowed to propagate further up the call stack, possibly to a catch
block in another method, or possibly to the point where it kills the program. A single try/catch block does
not have to handle all possible errors that could occur.
Throwing Exceptions
It’s time to look at the other side of this equation: creating and throwing exceptions yourself! Don’t get
carried away with throwing exceptions. If the method that discovers a problem knows the solution to the
problem, don’t bother throwing anything. Just handle the problem and continue on. To generalize that
statement, the closer you handle an error to the place it was detected the better.
To throw an exception, simply use the throw keyword with an instance of some Exception class:
public void CauseTrouble() // Always up to no good...
{
throw new Exception("Just doing my job!");
}
198
Chapter 30
Error Handling and Exceptions
It kind of feels like the return statement, but it throws exceptions instead of returning a value. You create
the Exception object, just like any other object, with the new keyword, and away it goes.
You’re not limited to using the Exception class. (It’s preferable not to.) There are many options already
defined. For instance, here’s a small list of some common exceptions and what they’re for:










NotImplementedException: Used to indicate that a method hasn’t been implemented yet.
Visual Studio will frequently put this in when you use it to automatically generate a method.
IndexOutOfRangeException: Used when you access an index beyond the size of a collection.
InvalidCastException: Used when you try to cast to a type that wasn’t the right kind of object.
FormatException: Used when text was not in the right format for converting to something else.
NotSupportedException: You tried to do an operation that wasn’t supported. For instance, make
a method call at a time that didn’t allow it.
NullReferenceException: A reference type contained null instead of an actual object.
StackOverflowException: You see this when you run out of space on the stack. This is usually a
result of recursion that went bad.
DivideByZeroException: You tried to divide by zero and got caught.
ArgumentNullException: One of the arguments or parameters that you gave to a method was
null, but the method requires something besides null.
ArgumentOutOfRangeException: An argument contained a value that the method couldn’t
intelligently deal with (e.g., it required a number between 1 and 10, but you gave it 13).
If you can’t find an existing exception type that fits, you can create your own. All you need to do is create a
class that is derived from Exception or from another exception class. This will look something like this:
public class AteTooManyHamburgersException : Exception
{
public int HamburgersEaten { get; set; }
public AteTooManyHamburgersException(int hamburgersEaten)
{
HamburgersEaten = hamburgersEaten;
}
}
With this class, you can now say:
throw new AteTooManyHamburgersException(125);
And:
try
{
EatSomeHamburgers(32);
}
catch(AteTooManyHamburgersException e)
{
Console.WriteLine($"{e.HamburgersEaten} is too many hamburgers.");
}
As a general rule, you should throw different exception types whenever the calling code would want to
handle the errors in different ways. If your method could fail in two different ways, you should be
throwing two different exception types. This allows people to use different catch blocks to handle them
differently. If there’s no good pre-made exception type for you to use, make your own.
I bring this up because it is really easy to get lazy and start always throwing just the plain old Exception
type. Don’t get lazy; use the right exception types, even if that means creating your own.
The ‘finally’ Keyword
199
The ‘finally’ Keyword
When you throw an exception, the flow of execution stops going through the method immediately. What if
you had done something with significant side effects, like having a file open?
If everything had gone according to plan, you would have been able to close the file in an intelligent way,
but since a problem came up and an exception is being thrown, that cleanup code wouldn’t ever be
executed, leaving our file open. The finally keyword allows us to address this problem.
At the end of a try-catch block, you can have a finally block (making it a try-catch-finally block). The
code inside of the finally section will get executed no matter what. If it ran through the try block without
errors, it will get executed. If it ran into a return statement, it will get executed. If there was an exception,
it will get executed (just before jumping up to the calling method with the exception).
Here’s an example of how this works:
try
{
// Do some stuff that might throw an exception here
}
catch (Exception)
{
// Handle the exception here
}
finally
{
// This code will always get execute, regardless of what happens in the try-catch block.
}
This is important, so let me repeat myself: the stuff in the finally block gets executed no matter how you
leave the try-catch block. Error, return statement, reaching the end. It doesn’t matter.
Related to this, you’re not allowed to put return statements inside of finally blocks. We may already be
returning when we hit the finally block, or we may be in the process of handling an exception. Allowing
return statements here just doesn’t make any sense, so it’s banned.
This brings up an interesting point. finally blocks are not just for exception handling. It does have plenty
of value in those situations, but it can also be used by itself, with no mention of throwing or catching
exceptions:
private static int numberOfTimesMethodHasBeenCalled = 0;
public static int RandomMethod(int input)
{
try
{
if(input == 0) return 17;
if(input == 1) return -2;
if(input == 2) return -11;
return 5;
}
finally
{
numberOfTimesMethodHasBeenCalled++;
}
}
In this case, no matter how we return from the method, the code in the finally block will always get
executed. (There’s a lot of better ways to write this particular code, but it illustrates the point.)
200
Chapter 30
Error Handling and Exceptions
Exception Filters
Sometimes, exception types are not quite fine-grained enough. For example, some libraries will reuse the
same exception class, but include an error code or use different text in the Message property of the
exception to indicate subtly different errors. If all of these errors are the same type, it gets harder to
separate out the different error types to handle (or not handle) in different ways.
One option is to catch the exception, check for certain conditions and handle the things you want, and
then rethrow anything left over:
try
{
throw new Exception("Message");
}
catch(Exception e)
{
if (e.Message == "Message") { Console.WriteLine("I caught an exception."); }
else { throw; }
}
The above is called “rethrowing the exception.” It allows you to throw the exact same exception that was
caught, with one caveat. The exception’s stack trace ends up changing, so that its location now refers to
the location where it was rethrown, rather than where it was thrown from originally. This can make it
tough to track down the original problem.
The other way to handle this is with exception filters. Exception filters allow you to add a bit of code to a
catch block that can be used to filter whether an exception is actually caught or not. The syntax is similar
to an if-statement, but uses the when keyword instead:
try
{
throw new Exception("Message");
}
catch(Exception e) when (e.Message == "Message")
{
Console.WriteLine("I caught an exception.");
}
This approach is preferable to filtering by placing an if-statement inside of the catch block. The exception
filter syntax is usually clearer, and does not cause the exception’s stack trace to be changed.
Some Rules about Throwing Exceptions
When you are throwing exceptions, there are a few guiding principles that you should try to follow.
Throwing exceptions can really do some crazy things to the flow of execution in your program, and so we
want to take care to prevent bad things from happening when you do so.
At a bare minimum, you should not leave resources open that were opened before the exception was
thrown. They should be closed or cleaned up first. This can usually be handled in a finally clause. But at a
minimum, make sure that your program is still in a safe state.
Second, you want to revert back to the way things were before the problem occurred, so that once the
error has been handled, people know the system is still in a valid state. A finally block can do this too.
Lastly, if you can avoid throwing an exception in the first place, that is what you should do.
31
31 Pattern Matching
In a Nutshell




A pattern is a syntactic element that allows for conditional execution of code.
A pattern has four parts:
 An input value.
 A rule or condition that determines whether the pattern is a “match” or not.
 A section of code that is executed when the pattern is a match.
 An output value or result that is produced from the pattern.
C# supports three patterns:
 The constant pattern, which matches when the input equals a specific constant.
 The type pattern, which matches when the input is of a specific type.
 The var pattern, which always matches.
C# allows you to use patterns in two places: switch statements and with the is keyword.
C# 7 introduced a concept called pattern matching. It is a concept that has been floating around in other
languages (primarily functional programming languages like F#) for a while, and C# has it now as well.
This chapter outlines what patterns are at a conceptual level, identifies the patterns that are available in
C#, and illustrates where patterns can be used in C#: switch statements and with the is keyword.
Contrasted with Regular Expressions
Before diving into patterns, I want to disambiguate the C# language feature of patterns and pattern
matching with another useful tool called regular expressions, which are also sometimes referred to as
patterns or pattern matching. A C# pattern is not the same thing as a regular expression. The two are
conceptually related, and C# is capable of doing regular expression searches, but the two are different.
While regular expressions are not on topic for this book (it’s a whole other mini-language) a brief overview
is probably in order. It’s a useful (if not complicated) tool for performing text searches. They are defined
by a special syntax or language where you specify a pattern to look for (like a valid email address, a phone
number, a credit card, etc.) which can then be applied to strings to detect those patterns in it. They’re a
powerful tool, and if you end up doing a lot of text searching, it will be worth your time to learn it.
202
Chapter 31
Pattern Matching
C# is quite capable of using regular expressions. There’s a whole namespace that handles regular
expression searching (System.Text.RegularExpressions). Microsoft has a pretty good overview of this here:
https://msdn.microsoft.com/en-us/library/ms228595.aspx.
If you’re not familiar with regular expressions but want to be, here’s a pretty good website that could
serve as a starting point: http://www.regular-expressions.info/quickstart.html.
But enough about regular expressions. That’s not what this chapter is about.
The Pattern Concept
A pattern is a small syntactic element—a small chunk of code—that allows for conditional execution of
some code based on a rule or logical condition.
There are four major pieces to the pattern concept:
1.
2.
3.
4.
An input. Patterns operate on some input data that will be evaluated for a match.
A rule. Patterns are defined by some sort or rule. This is a chunk of logic that can be executed,
and boils down to a true or false value that indicates whether the pattern was a match or not. A
match means that the rule evaluated to true for the given input.
An action. A pattern will provide some specific additional logic—an action, a return value, or a
side effect—that is to be performed only when the rule matches.
An output. When a rule matches, the action is executed and an output is produced.
The following diagram illustrates how these pieces work together:
That’s all fairly abstract. The coming examples should help illustrate the point more clearly.
As of C# 7, there are only three patterns that are available, and only two places where patterns can be
used. Though even just that small sampling of patterns is enough to begin to illustrate the power of
patterns, and give us a glimpse into the future of C#.
Available Patterns
There are three patterns available in C# 7. In this section, we will cover those patterns briefly. But those
patterns will probably make a lot more sense when we actually put them to use in the next section. So
we’ll skip some of the details until after the practical examples later on.
The Constant Pattern
The simplest pattern is the constant pattern. When you see this pattern, it will show up as a compile-time
constant, like 0.0, 42, null, true, etc. This pattern will evaluate to true—that is, it will “match”—when the
input equals the constant specified.
The Type Pattern
The second pattern is called the type pattern. This is a pattern that matches when the input is of a specific
type. This pattern has the form T x, where T is some type name, and x is some variable name. For
example, all of the following could be a valid fit for the type pattern: int n, string message, or Point p. It
Using Patterns in C#
203
looks a bit like a variable declaration (and in some senses kind of is a variable declaration) but the context
will make it clear if something is a variable declaration or the type pattern. When the type pattern
matches, it produces a new variable with the type and name specified, which can be used afterwards.
This pattern is a little more useful than the constant pattern, and does a little better job of illustrating the
value of patterns in the first place. This is probably single most useful pattern that C# has.
We’ll see practical uses of this pattern in a minute.
The Var Pattern
The final pattern is the var pattern, or the variable pattern. This pattern will be written with the var
keyword and a variable name, similar to the type pattern. For example, var n, var message, or var p.
Unlike the type pattern, the var pattern always evaluates to true, and essentially just copies the input
value into a new variable with the name supplied in the pattern. Because of this, the var pattern is often
called a wildcard pattern, because it will always be a match.
Using Patterns in C#
Having introduced the three patterns in C#, it’s time to get into some more practical examples, and look at
how we would actually use these patterns. C# allows patterns to be used in two places: switch statements
(Chapter 11) and with the is keyword (Chapter 22). We’ll look at examples of both of these.
Patterns in Switch Statements
The first place we’ll look at is with switch statements. Back in Chapter 11, when we first saw switch
statements, we looked at code that used the constant pattern in switch statements. Consider the code
below, which gets a value back from some method and prints out different results based on that value:
int number = GetNumberFromSomewhere();
switch(number)
{
case 0:
Console.WriteLine("The number was 0");
break;
case 1:
Console.WriteLine("The number was 1");
break;
}
The code marked above (the 0 and the 1) are both actually uses of the constant pattern. A switch
statement is actually just a way to evaluate patterns one after another, until one of them matches.
Let’s extend our example and make it so it is operating on an object, rather than an int. This will allow us
to also apply the type pattern and the var pattern as well.
object value = GetValueFromSomewhere();
switch(value)
{
case 0:
Console.WriteLine("The value was
break;
case 1:
Console.WriteLine("The value was
break;
case int number:
Console.WriteLine("The value was
break;
case string text:
Console.WriteLine("The value was
break;
the number 0");
the number 1");
another number: " + number);
the following text: " + text);
204
Chapter 31
Pattern Matching
case var unknownType:
Console.WriteLine("The value was of an unknown type: " + value.ToString());
break;
}
This example goes much further in illustrating exactly how patterns work. Each element after a case label
is a different pattern.
In the first two case statements, they are constant patterns, where the constant is 0 and 1 respectively.
In the next two case statements, we use the type pattern (int number and string text). Remember, with
the type pattern, the pattern will only match if the input is of the correct type. Before the switch
statement, we only know that value is of type object. We don’t know which (if any) derived type it might
be. It could be an int, a string, a double, or anything else. If it turns out to be an int, then that third case
statement (case int number) will be a match, and that block will be executed. The input value is placed in
an int type variable called number. The block of code for that case statement can then use that variable.
In the event that the value is not an int, the third case statement will not match, and the next case
statement will be evaluated. That one is another type pattern, this time looking for strings. If the value is
a string, then it will match and the value will be put into a string typed variable named text.
If all four of the first switch statements fail, it falls down to the last one, which is the var pattern.
Remember that the var pattern always matches, which makes it kind of a default value. (Speaking of that,
we could have used a default: label here like we did in Chapter 11 with the same functionality.) Because it
always matches, this block will be entered if none of the earlier case statements were a match. Like
before, you can see that our unknownType variable is usable in the code block that follows it. The
unknownType variable will have the same type as the input value (object in this case).
If you put other case statements after this var pattern, they would never execute. That is because only
one block in the case statement will run. Since the var pattern always matches and the case expressions
are evaluated in order, the var pattern will gobble up everything that makes it that far.
On a related note, the type pattern matches with any value of the correct type. So if we had put the case
int number before the case 0 and case 1, it would have matched first, and nothing would have gotten in
to the constant patterns.
The general rule in a switch statement is that you should progress from more specific patterns to more
general patterns.
Patterns with the ‘is’ Keyword
The second place that patterns can be used is with the is keyword. We already saw one way that you can
use the is keyword, which is a direct way to check that something is of a specific type like so:
if(someVariable is Point)
{
// ...
}
You may notice this is almost the type pattern, but not quite. (Using a type with no variable name is not a
pattern and can’t be used in switch statements, for example. It’s a unique feature of the is keyword.)
But using the type pattern with the is keyword looks pretty similar:
if(someVariable is Point p) // Using the type pattern.
{
Console.WriteLine(p.X + ", " + p.Y);
}
Expect Patterns to Expand
205
In fact, this is a very useful way to do the task of both checking if something is a specific type and if so,
getting it into a variable of that type. The type pattern performs the check and the transformation to the
new type in a single step. Without patterns, you would have to do both of those things as separate
operations, perhaps something more like this:
if(someVariable is Point) // Not using a pattern
{
Point p = (Point)someVariable; // Casting is required to transform the type.
Console.WriteLine(p.X + ", " + p.Y);
}
Or maybe this code, which uses the as keyword instead:
Point p = someVariable as Point; // Using the as keyword,
if(p != null)
// which requires a null check.
Console.WriteLine(p.X + ", " + p.Y);
You can see that the earlier version that uses the type pattern is way more succinct and understandable.
The type pattern is the most useful pattern with the is keyword, but the other patterns could be used
here as well. For example, here is the constant pattern:
if(someVariable is null) { /* ... */ }
We could have used the == operator instead with the same functionality (and == works for things that
aren’t compile time constants as well.) You may find you prefer one syntax over the other in different
cases. Like usual, prefer the one that makes the code the most understandable and easy to work with.
The var pattern also works with the is keyword, though because it always matches, it isn’t very practical:
if(someVariable is var x) // var pattern works, but...
Console.WriteLine(x); // this could have been `Console.WriteLine(someVariable);` instead.
Expect Patterns to Expand
Patterns are a new concept in C# 7, but they’ve been around in other programming languages for quite a
while. Because patterns are so new, the current stuff only really gives you a small taste of what patterns
could be in future versions of the language.
You should expect patterns to expand in future versions of C#, both in regard to the types of patterns
available as well as the places that patterns can be used.
Try It Out!
Patterns Quiz. Answer the following questions to check your understanding. When you’re done,
check your answers against the ones below. If you missed something, go back and review the section
that talks about it.
1.
2.
3.
4.
5.
Name the four parts to a pattern.
Name three patterns that are available in C#.
Name two places that a pattern can be used in C#.
True/False. The type pattern always matches.
True/False. The var pattern always matches.
Answers: (1) input, rule, action, output. (2) constant, type, and var patterns. (3) Switch statements, is keyword. (4) False.
(5) True.
32
32 Delegates
In a Nutshell




A delegate type can store methods directly, allowing you to treat methods like an object.
To create a delegate, you use the delegate keyword, like this: public delegate float
MathDelegate(float a, float b);. This delegate can now be used to keep track of any method
with the same return type and the same parameter list.
You can then use a delegate in a way that is very similar to a variable: MathDelegate
mathOperation = Add;
You can also call the method that is being stored in the delegate variable: float result =
mathOperation(5, 7);
Delegates: Treating Methods like Objects
We’ve done just about everything you can think of with variables. We’ve put numbers in them, words in
them, and true/false values in them. We’ve created enumerations to define all possible values a variable
can take on, and we’ve created structs and classes to store complicated variables with multiple parts,
including methods to operate on that data.
Now we’re going to take it a step further. What if we had a type of variable that we could put a method in?
What if we could assign a method to a variable and pass it around, just like we’ve done with all of our
other types of data? C# has this feature, and it is called a delegate. Treating methods like data may seem
kind of strange at first, but there are many good uses for it.
Creating a Delegate
Having laid out the background, it is time to take a look at how you’d actually create a delegate. Not all
methods are interchangeable with others. With regular data, when we have this problem, we simply use
different data types. Methods have similar limitations, and delegates will have to account for that. When
we create a delegate, we’re going to say, “This type of delegate can store any method that has these
parameters, and this return type.”
Using Delegates
207
Setting up a delegate is relatively straightforward. Defining a delegate is considered defining a new type,
and as such, it is typically placed directly inside of a namespace, like we do with classes and structs.
This will probably make more sense with an example, so let’s do that by creating a MathDelegate, which
can be used to keep track of any method that can take two ints as input, and return an int. This could be
used for things like an Add method, which takes two numbers, adds them together, and returns the
result. Or a Multiply method, which takes the two numbers and multiplies them.
So to start, we’d create a brand new file for our delegate with a matching name. (If we’re going to call our
delegate MathDelegate, we’d call the file MathDelegate.cs, like usual.) In that file, to create a delegate,
we’d simply put something like the following in there:
public delegate int MathDelegate(int a, int b);
So our whole MathDelegate.cs file should look something like this:
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace Delegates
{
public delegate int MathDelegate(int a, int b);
}
Looking at this, you can probably see that this has a lot of similarities to declaring a method, with the
exception of adding in the delegate keyword. As we’ll soon see, we’ll be able to create variables that use
this delegate type (MathDelegate) to store any method that has this same parameter list and return type.
(That is, requires two int parameters and returns an int.)
Using Delegates
The first thing we need to use a delegate is some methods that match the delegate’s requirements: two
int parameters and returns an int. I’ve gone back to my main Program class, where the Main method is
located, and created these three methods, all of which match the requirements of the delegate:
public static int Add(int a, int b)
{
return a + b;
}
public static int Subtract(int a, int b)
{
return a - b;
}
public static int Power(int baseNumber, int exponent)
{
return (int)Math.Pow(baseNumber, exponent);
}
To create a delegate-typed object, we simply create a variable with a delegate type, like the
MathDelegate type we just created. Like with a normal variable, we can assign it a value, but in this case,
that value will be the name of a method that matches the delegate:
MathDelegate mathOperation = Add;
int a = 5;
208
Chapter 32
Delegates
int b = 7;
int result = mathOperation(a, b);
On the last line, you see we can also use the variable, invoking whatever method it happens to be storing
at that particular point in time. Doing this looks like a normal method call, except that it “delegates to” or
hands off execution to whatever method happened to be stored in the delegate variable at the time.
If you’ve never seen anything like this before, it may look kind of strange. But I hope you can start to
imagine how delegates could be helpful, allowing you to dynamically change which method is called at
runtime. As we move through the next few chapters, we’ll see how they can be used as a part of events
(Chapter 33), lambda expressions (Chapter 37), and query expressions (Chapter 38). They will be the
foundation for a lot of really powerful features in C#.
The Delegate and MulticastDelegate Classes
Now that we’ve got a basic understanding of C#’s syntax when dealing with delegates, it’s time to look
under the hood and see what the C# compiler does with a delegate that you define. This will help explain
some of the nuances of delegates, and should help you see delegates from another perspective.
A delegate is just syntactic sugar around a class. The .NET Platform defines a Delegate class. When you
create a delegate, the C# compiler will turn it into a class that is derived from Delegate. Specifically, it will
be derived from MulticastDelegate, which is derived from Delegate.
You are not allowed to create a class that directly derives from either of these manually (class MyClass :
MulticastDelegate is not legal code). Only the compiler is allowed to derive from these special classes.
The Delegate and MulticastDelegate classes define the core functionality of delegates. On top of that,
the C# language itself heaps a thick slab of magic and a pile of syntactic sugar on top to provide
programmers with cleaner syntax for working with delegates.
The key thing to learn from this is that it’s just a class, like the many other classes that we’ve now dealt
with. This has several implications that are worthy of mention:



You can declare a new delegate type anywhere you can declare a new class.
A variable that stores a delegate can contain null.
Before invoking the delegate, you should check for null first if there is any possibility that the
variable actually contains a null reference.
When you create a delegate type, the class that the C# compiler will generate is derived from
MulticastDelegate. As an example, the MathDelegate delegate we made in the last section is converted
into something that looks like this:
public class MathDelegate : System.MulticastDelegate
{
// Constructor
public MathDelegate(Object object, IntPtr method);
// This key method matches the definition of the delegate.
public virtual int Invoke(int a, int b);
// Two methods to allow the code to be called asynchronously.
public virtual IAsyncResult BeginInvoke(int a int b);
public virtual void EndInvoke(IAsyncResult result);
}
The constructor defined there allows the system to create a new instance of this delegate class. The
second parameter of the constructor, with the type IntPtr, references the method that you’re trying to
Delegate Chaining
209
put into the delegate. If the method is an instance method (not static) the object parameter will contain
the instance it is called with. If you create a delegate from a static method, this will be null.
Earlier, we had code that did this:
MathDelegate mathOperation = Add;
The compiler will roughly translate this into something like this:
MathDelegate mathOperation = new MathDelegate(null, Add);
That’s just an approximation, and there’s a lot of hand waving in there. I’m completely skipping over the
part where the compiler turns the method name of Add into an actual IntPtr, but that information just
isn’t available at the C# language level. It’s only known by the compiler.
Earlier, we called the delegate method by simply using parentheses and putting our parameters inside:
int result = mathOperation(3, 4);
In this case, the C# compiler will turn this into a call to the Invoke method it created:
int result = mathOperation.Invoke(3, 4);
Written this way, it’s a little easier to see why you might want to check for null before invoking the
method, just in case the mathOperation variable is null.
While most C# programmers like the first version of calling a delegate (without the Invoke) the second
version is both legal (the C# compiler will let you directly call Invoke if you want) and is also preferred by
some C# programmers. So feel free to use it if it seems preferable or more readable to you.
We’ll see more of this syntactic sugar in the next section when we look at chaining.
Delegate Chaining
With a name like “multicast,” you’d imagine there’d be a way to have a delegate work with multiple
methods. And you’re right!
Things get a little complicated when our delegate returns a value, so let’s come up with another example
instead of the MathDelegate we were looking at earlier. This time, one that doesn’t return a value. It just
does something with some input.
Let’s design a really basic logging framework. We can start with a simple LogEvent class that has a Text
property. Obviously, a real logging framework would probably have quite a bit more than this, but let’s
keep it simple. We’re just trying to do some delegate stuff here.
public class LogEvent
{
public string Text { get; }
public LogEvent(string text)
{
Text = text;
}
}
We’ll also define a simple delegate type to handle or process these LogEvents in various ways:
public delegate void LogEventHandler(LogEvent logEvent);
We could easily imagine all sorts of things we could do with an event handler method. Writing things to
the console is an obvious first choice. We could also write them to a file or to a database. If we added a
210
Chapter 32
Delegates
Severity property to our LogEvent class, we might even set something up so that if a Fatal level log event
came in, we’d send an email somewhere.
Let’s just stick with the low hanging fruit here and write a method that can write a LogEvent to the
console and another one that can write it to a file:
private static void LogToConsole(LogEvent logEvent)
{
Console.WriteLine(logEvent.Text);
}
private static void LogToFile(LogEvent logEvent)
{
File.AppendAllText("log.txt", logEvent.Text + "\n");
}
These don’t have to be static of course, but since they don’t rely on any instance variables or methods, I’ve
made them static.
The first method simply writes to the console, which we’ve done a million times now.
The second one writes to a file using the static AppendAllText method on the File class. It writes to a file
called log.txt, and since it’s a relative path, you’ll find it right next to the application’s EXE file. I also stuck a
new line character (‘\n’) at the end of that so that each log event will show up on its own line. In the case of
the console version, WriteLine always appends a new line character at the end by default anyway, so we
don’t need to do that there.
OK, on to the good stuff. Multicast delegates.
Before, when we wanted the delegate to use just a single method, we assigned it the method like this:
LogEventHandler logHandlers = LogToConsole;
So how do we get a second method in there as well?
The easiest way is with the += operator:
LogEventHandler logHandlers = LogToConsole;
logHandlers += LogToFile;
Now when we invoke the delegate, both methods in the delegate will be called:
logHandlers(new LogEvent("Message"));
Since both the LogToConsole method and LogToFile method have been added to the delegate, both
things will happen, and we’ll get the message pumped to the console and to the file.
Taking a particular method out of a delegate is simply a matter of using the -= operator:
logHandlers -= LogToFile;
It’s useful to keep in mind that what we’re doing here with the += and -= operators is syntactic sugar, built
around some static methods that belong to the Delegate class. These methods are the Combine and
Remove methods respectively. The following two lines of code are functionally equivalent:
logHandlers += LogToFile;
logHandlers = (LogEventHandler)Delegate.Combine(logHandlers, new LogEventHandler(LogToFile));
Likewise, the following two lines are also equivalent and remove a method from the delegate chain:
logHandlers -= LogToFile;
logHandlers = (LogEventHandler)Delegate.Remove(logHandlers, new LogEventHandler(LogToFile));
The Action and Func Delegates
211
Generally speaking, the simplified version using += or -= is clearer and simpler, so it is preferred.
Side Effects of Delegate Chaining
Adding multiple methods to a delegate literally creates a chain of objects, linked together by references.
(If you’re familiar with the concept of a linked list, that’s essentially how it’s implemented.) This has a
couple of side effects that are worth noting.
The first thing is a multicast delegate’s return value. Earlier, we sidestepped that question by using a
delegate with a void return type, but it should be stated that the return value of a multicast delegate is
the result of the final method. All of the other results are completely ignored. This makes multicast
delegates somewhat less useful for non-void return values. In practice, multicast delegates are generally
only used for signatures with a void return type anyway. (You can technically go digging into a delegate’s
invocation list and gather return values, but there is no easy way to do that, so it’s rarely done.)
The second catch is in exception handling. If a method invoked by a delegate throws an exception, none
of the handlers further down the chain will be invoked. For this reason, you should do everything you can
to avoid throwing exceptions in methods that get attached to a delegate.
The Action and Func Delegates
You can make your own delegate types whenever you need to, but in practice, this is rare. Included with
the .NET Platform is a collection of delegates that are already defined for you. These delegates are
generic, so they are very flexible, and cover most situations. These are the Action and Func delegates.
The Action delegates all have a void return type, while the Func delegates have a generic return type.
There’s a whole set of these that are each a little different. For instance, there’s the plain old simple
Action delegate, which looks like this:
public delegate void Action();
Then there’s a version that has one generic parameter, two generic parameters, three generic
parameters, and so on, up to 16. (That’s crazy talk, right there!)
public delegate void Action<T>(T arg);
public delegate void Action<T1, T2>(T1 arg1, T2 arg2);
public delegate void Action<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3);
So as an example, if your method has a void return type, and requires two ints as parameters, you could
use the Action delegate with two parameters, putting int in for both type parameters:
Action<int, int> myDelegate = MethodThatRequiresTwoInts;
myDelegate(3, 4);
If you need to return something, you can use one of the Func delegates instead. These have a similar
pattern, but return a value (of a generic type):
public delegate TResult Func<TResult>();
public delegate TResult Func<T, Result>(T arg);
public delegate TResult Func<T1, T2, Result>(T1 arg1, T2 arg2);
Again, this goes up to 16 input type parameters, plus the generic result parameter. So instead of our little
MathDelegate that we made, we could have used Func<int, int, int> instead:
Func<int, int, int> mathOperation = Add;
int a = 5;
int b = 7;
int result = mathOperation(a, b);
33
33 Events
In a Nutshell






Events allow one section of code to notify other sections that something has happened.
Events rely on delegates to do their work.
To create an event inside of a class, you would use code similar to this: public event
EventHandler PointChanged;. In this case, EventHandler is the name of the delegate event
handlers must use, and PointChanged is the name of the event.
You raise events by first checking to make sure that there are event handlers attached then
raising the event: if(PointChanged != null) { PointChanged(this, EventArgs.Empty); }
Attach methods to an event with the += operator: PointChanged += HandlerMethod;
Detach methods from an event with the -= operator: PointChanged -= HandlerMethod;
One of the coolest things about C# is a feature called events. Events allow classes to notify others when
something specific happens. This is extremely common in GUI-based applications where there are things
like buttons and checkboxes. These things have the ability to indicate when something of interest
happens, like the button was pressed, or the user checked the checkbox other objects can be notified of
the change and can handle the event in whatever way they need to.
User interfaces are not the only use for events. Any time that you have one class that has interesting
things happening, other classes will want to know about it. Events are very useful in these situations. It
keeps the two classes separated and allows the “client” or listener to attach itself to events generated by
another object when they’re interested, and detach itself when it doesn’t care anymore.
This is similar to how we use things like Twitter or an RSS reader to subscribe to blogs. When there is
something out there with updates that we are interested in, we subscribe to it. While we’re subscribed, we
see any new updates they have. When we discover that what we were paying attention to was sacked by
llamas and there won’t be any more notifications that we care about, we unsubscribe and move on. The
blog or Twitter account will continue making notifications for the sake of anyone still listening.
The advantage of this kind of a model is that the object that is raising events doesn’t need to know or care
about who is attached or listening to any particular event, nor is it responsible for getting listeners
registered or unregistered. The listener makes its own decisions about when to start and stop listening.
Defining an Event
213
In the previous chapter, we talked about delegates. Delegates are going to be a key part of events, so it is
important to understand the basics of how they work before jumping into events.
Defining an Event
Our first step will be to create an event. Let’s say we have a class that represents a point in two
dimensions. So it has an x-coordinate and a y-coordinate. This class might look like this:
using System;
namespace Events
{
public class Point
{
private double x;
private double y;
public double X
{
get { return x; }
set { x = value; }
}
public double Y
{
get { return y; }
set { y = value; }
}
}
}
To define an event inside a class, we’ll need to add a single line of code as a member of the class:
public event EventHandler PointChanged;
So now our code should look like this:
using System;
namespace Events
{
public class Point
{
private double x;
private double y;
public double X
{
get { return x; }
set { x = value; }
}
public double Y
{
get { return y; }
set { y = value; }
}
public event EventHandler PointChanged;
}
}
Like other members (properties, methods, and instance variables) we can use public, internal,
protected, or private for events, though events are usually public or internal.
214
Chapter 33
Events
The event keyword is what makes this particular member an event that others can attach to.
We also specify the delegate type of methods that can be attached to the event. Remembering how
delegates work, this means that all listener methods will be required to have a specific set of parameters,
as well as a specific return type (though with events, it is almost invariably void). In this case, we use the
EventHandler delegate, which is a pre-defined delegate specifically made for really simple events. This
delegate has a return type of void and has two parameters, an object, which is the “sender” of the event,
and an EventArgs object. The EventArgs object stores some basic information about the event itself.
Any delegate type can be used here. You’re not just limited to EventHandler.
Raising an Event
Now that we have an event, we need to add in code to raise the event in the right circumstances.
Don’t add this into your code quite yet, but consider the following code, which raises an event:
if(PointChanged != null)
PointChanged(this, EventArgs.Empty);
This little bit of code is pretty simple. We check to see if the event is null. If the event is null, there are no
event handlers attached to the event. (In a second, we’ll see how to attach event handlers.) Raising an
event with no event handlers results in a NullReferenceException, so we need to check this before we
raise the event.
Once we know the event has event handlers attached to it, we can raise the event by calling the event with
the parameters required by the delegate—in this case, a reference to the sender (this) and an EventArgs
object (though we’re just using the static EventArgs.Empty object in this case).
While this code to raise an event can technically go anywhere, I usually put it in its own method. It is very
common to name these methods the same as the event, but with the word “On” at the beginning:
OnPointChanged, in our particular case.
So we can add the following method to our code:
public void OnPointChanged()
{
if(PointChanged != null)
PointChanged(this, EventArgs.Empty);
}
When we detect that the conditions of the event have been met, we call this method to raise the event.
In this particular case, since we want to raise the event any time the point changes, we’ll want to call this
method when the value of X or Y gets set. To accomplish this, we’ll add a method call to the setters of
both of these properties:
public double X
{
get { return x; }
set
{
x = value;
OnPointChanged();
}
}
public double Y
{
get { return y; }
set
Attaching and Detaching Event Handlers
215
{
y = value;
OnPointChanged();
}
}
Our completed code, including everything to add and raise the event, looks like this:
using System;
namespace Events
{
public class Point
{
private double x;
private double y;
public double X
{
get { return x; }
set
{
x = value;
OnPointChanged();
}
}
public double Y
{
get { return y; }
set
{
y = value;
OnPointChanged();
}
}
public event EventHandler PointChanged;
public void OnPointChanged()
{
if(PointChanged != null)
PointChanged(this, EventArgs.Empty);
}
}
}
Attaching and Detaching Event Handlers
Now that we’ve got our Point class set up with an event for whenever it changes, we need to know how to
attach a method as an event handler for the event we’ve created.
The way we attach something to an event is by giving the event a method to call when the event occurs.
The method we attach is sometimes called an event handler. Because events are based on delegates we’ll
need a method that has the same return type and parameter types as the delegate we’re using—
EventHandler in this case. The EventHandler delegate we’re using requires a void return type, and two
parameters, an object that represents the sender (the thing sending the event) and an EventArgs object,
which has specific information about the event being raised.
Anywhere that we want, we can create a method to handle the event that looks something like this:
public void HandlePointChanged(object sender, EventArgs eventArgs)
{
216
Chapter 33
Events
// Do something intelligent when the point changes. Perhaps redraw the GUI,
// or update another data structure, or anything else you can think of.
}
It is a simple task to actually attach an event handler to the event. Again, the following code can go
anywhere you need it to go, where the method is in scope (accessible):
Point point = new Point();
point.PointChanged += HandlePointChanged;
// Now if we change the point, the PointChanged event will be raised and HandlePointChanged is called.
point.X = 3;
The key line there is the one that attaches the handler to the event: point.PointChanged +=
HandlePointChanged;. The += operator can be used to add the HandlePointChanged method to our
event. If you try to attach a method that doesn’t match the needs of the event’s delegate type, you’ll get an
error when you go to compile your program.
You can attach as many event handlers to an event as you want. In theory, all event handlers attached to
an event will be executed. However, if one of the event handlers throws and exception that isn’t handled,
the remaining handlers won’t get called. For this reason, event handlers shouldn’t throw any exceptions.
It is also possible to detach event handlers from an event, which you should do when you no longer care
to be notified about the event. This is an important step. Without detaching old event handlers, it’s easy to
end up with tons of event handlers attached to a method, or even the same event handler attached to an
event multiple times. In the best case scenario, things slow down a lot. In the worst case scenario, things
may not even function correctly.
Note that you rarely want the same event handler method attached to an event more than once, but this
is allowed. The method will be called twice, because it is attached twice.
To detach an event, you simply use the -= operator in a manner similar to attaching events:
point.PointChanged -= HandlePointChanged;
After this executes, HandlePointChanged will not be called when the PointChanged event occurs.
Common Delegate Types Used with Events
When using events, the event can use any delegate type imaginable. Having said that, there are a few
delegate types that seem to be most common, and those are worth a bit of attention.
The first common delegate is one of the Action delegates, which we talked about in the last chapter. For
instance, if you want an event that simply calls a parameterless void method when the event occurs (a
simple notification that something happened, without including any data to the listener) you could use
plain old Action:
public event Action PointChanged;
You could subscribe the method below to that event:
private void HandlePointChanged()
{
// Do something in response to the point changing.
}
If you need to get some sort of information to the subscriber, you could use one of the other variants of
Action that has generic type parameters. For example, we might want to pass in the point that changed
Common Delegate Types Used with Events
217
to the subscribers. So we could change the event declaration to use the generic Action that has an extra
type parameter to include the point:
public event Action<Point> PointChanged;
Then we subscribe to the event with a method like this:
private void HandlePointChanged(Point pointThatChanged)
{
// Do something in response to the point changing.
}
Using different versions of Action, we can pass any number of parameters to the event handlers.
Another common option is a delegate that works along these lines:
public delegate void EventHandler(object sender, EventArgs e);
In this example, we get a reference to the sender, who initiated the event, as well as an EventArgs object,
which stores any interesting information about the event inside it. In other cases, you may be interested
in using a more specialized type, derived from EventArgs, with even more information.
In this case, instead of using your own special delegate, you can use the EventHandler<TEventArgs>
event handler. (To use this, you must use EventArgs or another type that is derived from it.)
So let’s say you have an event that is keeping track of a change in a number, and in the notification
process, we want to see the original number and what it was changed to. We can make a class derived
from the EventArgs class like this:
public class NumberChangedEventArgs : EventArgs
{
public int Original { get; }
public int New { get; }
public NumberChangedEventArgs(int originalValue, int newValue)
{
Original = originalValue;
New = newValue;
}
}
Then we’d define our event like this:
public event EventHandler<NumberChangedEventArgs> NumberChanged;
This event would be raised like we saw earlier, usually in a method called OnNumberChanged:
public void OnNumberChanged(int oldValue, int newValue)
{
if(NumberChanged != null)
NumberChanged(this, new NumberChangedEventArgs(oldValue, newValue));
}
Methods that want to handle this event would then look like this:
public void HandleNumberChanged(object sender, NumberChangedEventArgs args)
{
Console.WriteLine($"The original value of {args.Original} is now {args.New}.");
}
You’re not restricted to using any of these delegates (events can use any delegate you want) but most
scenarios won’t actually require you to build a custom delegate type for an event if you don’t want to.
218
Chapter 33
Events
The Relationship between Delegates and Events
It’s a common misconception among C# programmers that events are just delegates that can be attached
to multiple methods, or that an event is just an array of delegates. Both of these ideas are wrong, though
it’s an easy mistake to make.
In the vast majority of cases, delegates are not used to call more than one method at a time, while events
quite often are. So many C# programmers don’t see delegates that reference more than one method at a
time, and make the incorrect assumption that they can’t handle it at all.
A more accurate way to think of it is more along the lines of an event being a property-like wrapper
around a delegate. Specifically, what we’ve seen up until now is similar to an auto-implemented property.
Let’s look back at our earlier example with our NumberChanged event:
public event EventHandler<NumberChangedEventArgs> NumberChanged;
The compiler expands this into code that looks something like this:
private EventHandler<NumberChangedEventArgs> numberChanged; // The wrapped delegate.
public event EventHandler<NumberChangedEventArgs> NumberChanged
{
add { numberChanged += value; }
// Defines behavior when a method subscribes.
remove { numberChanged -= value; } // Defines behavior when unsubscribing.
}
Wrapping the delegate like this prevents people from outside of the class from invoking the delegate
directly (in other words, you can’t raise an event from outside of the class that it exists in) as well as from
mucking with the methods that are contained in the delegate chain.
Interestingly, the code above is actually completely legal code. If you don’t like the default implementation
of an event, you can write code that looks like the previous sample and do something different with it. In
practice, it’s rare to actually need to deviate from the standard pattern, but it is allowed.
One complication with explicitly implementing the add and remove parts for an event like this is that you
can no longer invoke the event directly. Instead, you have to invoke the delegate that it wraps
(numberChanged instead of NumberChanged, in this specific case). That’s because you’ve created
custom behavior for what it means to add or remove a method to an event and the normal rules aren’t
guaranteed to apply. For instance, it’s possible that the event doesn’t even wrap a delegate at all anymore,
or that it subscribes or unsubscribes from multiple delegates. Since the compiler can’t guarantee what it
means to raise the event anymore, the programmer must determine what it means.
Try It Out!
Delegates and Events Quiz. Answer the following questions to check your understanding. When
you’re done, check your answers against the ones below. If you missed something, go back and
review the section that talks about it.
1.
2.
3.
4.
5.
True/False. Delegates allow you to assign methods to variables.
True/False. You can call and run the method currently assigned to a delegate variable.
True/False. Events allow one object to notify another object when something occurs.
True/False. Any method can be attached to a specific event.
True/False. Once attached to an event, a method cannot be detached from an event.
Answers: (1) True. (2) True. (3) True. (4) False. (5) False.
34
34 Operator Overloading
In a Nutshell







Operator overloading allows us to define how some operators work for types that we create.
Operator overloading works for these operators: +, -, *, /, %, ++, --, ==, !=, >=, <=, >, and <.
Operator overloading does not work for these operators: ’&&’ and ’||’, the assignment
operator ’=’, the dot operator ’. ’, or the new operator (keyword).
An example of overloading the ’+’ operator looks like this: public static Vector operator
+(Vector v1, Vector v2) { return new Vector(v1.X + v2.X, v1.Y + v2.Y); }
All operators must be public and static.
The relational operators must be done in pairs. (== and !=, < and >, <= and >=.)
Only overload operators when there is a single, unambiguous way to use the operation.
Throughout the course of this book, we’ve seen a lot of different operators. They all have built-in
functionality that does certain specific things. Mostly, these are defined by math, going back thousands of
years. In a few cases, we’ve seen some not-so-normal uses for these operators, like using the ‘+’ operator
for concatenating (sticking together) two strings (as in string text = "Hello" + "World";). In math, there’s
no way to add words together, but in C# we can do it.
When you create your own types, you can also define how some of these operators should work for them.
For example, if you create a class called Cheese, you may define what the ‘+’ operator should do, allowing
you to add two Cheese objects together (Though if you do that, Pepper Jack and Colby better result in
Colby-Jack!) This is called operator overloading, and it is a powerful feature of C#.
In a minute, we’ll look at how to actually overload these operators, but let’s start by discussing what
operators you’re even allowed to overload. Many but not all operators can be overloaded. The creators of
C# tried to allow you to overload as many as possible, but some would be too dangerous allow. These
operators can be overloaded: +, -, *, /, %, ++, --, ==, !=, >=, <=, >, and <. The compound assignment
operators (+=, -=, /=, *=, %=, etc.) can’t be directly overloaded, but overloading + allows += to be used,
overloading * allows *= to be used, and so on.
220
Chapter 34
Operator Overloading
The relational operators must be overloaded in pairs—if you overload the == operator, you must also
overload the != operator, and if you overload the > operator, you must also overload the < operator as
well, and so on.
On the other hand, the following operators cannot be overloaded: the logical operators && and ||, the
assignment operator (=), the dot operator (.), and the new operator. Being able to overload these
operators would just be too dangerous and confusing to anyone trying to use them. Take the assignment
operator, for example. We use it for things like int x = 3;. If you could make it do something else besides
putting the value of 3 into the variable x, it could cause some serious issues.
Note that the array indexing operator ([ and ]) can be overloaded, but it is done using indexers, which we’ll
look at in the next chapter.
Unfortunately, you can’t define your own brand new operator with operator overloading.
Overloading Operators
You can overload operators for any of your own types, but for the sake of simplicity, I’m going to pick a
class that should make some sense for overloading operators. I’ll show operator overloading for a Vector
class, which stores an x and y coordinate. Vectors show up all over in math and physics, but if you’re fuzzy
on the concept, you can think of a vector as a point (in this case, a 2D point).
So let’s start with the basic class as a starting point:
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace OperatorOverloading
{
public class Vector
{
public double X { get; set; }
public double Y { get; set; }
public Vector(double x, double y)
{
X = x;
Y = y;
}
}
}
Let’s say you want to add two vectors together. In math and physics, when we add two vectors, the result
is another vector, but with the x and y components added together. For example, if you add the vector (2,
3) to the vector (4, 1), you get (6, 4), because 2 + 4 = 6, and 3 + 1 = 4.
To overload the + operator, we simply add the following code as a member of the Vector class:
public static Vector operator +(Vector v1, Vector v2)
{
return new Vector(v1.X + v2.X, v1.Y + v2.Y);
}
So now your Vector class might look something like this at this point:
using System;
using System.Collections.Generic;
using System.Linq;
Overloading Operators
221
using System.Text;
using System.Threading.Tasks;
namespace OperatorOverloading
{
public class Vector
{
public double X { get; set; }
public double Y { get; set; }
public Vector(double x, double y)
{
X = x;
Y = y;
}
public static Vector operator +(Vector v1, Vector v2)
{
return new Vector (v1.X + v2.X, v1.Y + v2.Y);
}
}
}
All operator overloads must be public and static. This should make sense, since we want to have access
to the operator throughout the program, and since it belongs to the type as a whole, rather than a specific
instance of the type. We then specify a return type, Vector in this case. We use the operator keyword,
along with the operator that we’re overloading. We then have two parameters, which are the two sides of
the + operator.
For a unary operator like - (the negation operator or negative sign) we only have one parameter:
public static Vector operator -(Vector v)
{
return new Vector(-v.X, -v.Y);
}
Notice, too, that we can have multiple overloads of the same operator:
public static Vector operator +(Vector v, double scalar)
{
return new Vector(v.X + scalar, v.Y + scalar);
}
Now you can add a vector and a scalar (just a plain old number). Though from a math standpoint, this has
little practical value.
The relational operators can be overloaded in the exact same way, only they must return a bool:
public static bool operator ==(Vector v1, Vector v2)
{
return ((v1.X == v2.X) && (v1.Y == v2.Y));
}
public static bool operator !=(Vector v1, Vector v2)
{
return !(v1 == v2); // Just return the opposite of the == operator.
}
Remember that the relational operators must be overloaded in pairs, so if you overload ==, you must also
overload !=, as shown here.
If you look closely at these operator overloads, you can see that they just look like a method. (The only
real difference is the operator keyword.) The C# compiler converts this to a method behind the scenes.
222
Chapter 34
Operator Overloading
Overloading operators is done primarily to make our code look cleaner. It is what’s called syntactic sugar.
Anything that you can do with an operator, you could have done with a method. Overloading an operator
is sometimes more readable though.
Just because you can overload operators, doesn’t mean you should. Imagine that you overload an
operator to have it do something totally unexpected. If it isn’t clear what your overloaded operator does,
you’ll cause yourself and others lots of problems. It’s for this reason that Java and other languages, erring
on the side of being overly cautious, have chosen to not even allow operator overloading. The rule to
follow is to only overload operators that have a single, clear, intuitive, and widely accepted use.
With our operators defined, we can use them with the same syntax that we’ve seen before:
Vector a = new Vector(5, 2);
Vector b = new Vector(-3, 4);
Vector result = a + b;
// At this point, result is <2, 6>.
// We use the operator overload here.
Try It Out!
3D Vectors. Make a Vector class like the one we’ve created here, but instead of just x and y, also add
in z. You’ll need to add another property, and the constructor will be a little different. Add operators
that do the following:





Add two 3D vectors together. (1, 2, 3) + (3, 3, 3) should be (4, 5, 6).
Subtract one 3D vector from another. (1, 2, 3) - (3, 3, 3) should be (-2, -1, 0).
Negate a 3D vector. For example, using the negative sign on (2, 0, -4) should be (-2, 0, 4).
Multiply a vector by a number (scalar) so (1, 2, 3) * 4 should be (4, 8, 12).
Divide a vector by a number (scalar) so (2, 4, 6) / 2 should be (1, 2, 3).
Additionally, write some code to run some tests on your newly created 3D vector class and check to
see if everything is working.
Try It Out!
Operator Overloading Quiz. Answer the following questions to check your understanding. When
you’re done, check your answers against the ones below. If you missed something, go back and
review the section that talks about it.
1.
2.
3.
4.
5.
True/False. Operator overloading means providing a definition for what the built-in
operators do for your own types.
True/False. You can define your own, brand new operator using operator overloading.
True/False. All operators in C# can be overloaded.
True/False. Operator overloads must be public.
True/False. Operator overloads must be static.
Answers: (1) True. (2) False. (3) False. (4) True. (5) True.
35
35 Indexers
In a Nutshell




An indexer is a way to overload the indexing operator ([ and ]).
Defining an indexer works like a cross between operator overloading and a property: public
double this[int index] { get { /* get code here */ } set { /* set code here */ } }
Indexers can use any type as a parameter.
You can have multiple indices by simply listing multiple things where you define the indexer:
double this[string someTextIndex, int numericIndex].
In the previous chapter, we talked about how to overload operators. In this chapter, we’ll talk about
indexers, which are essentially a way to overload the indexing operator ([ and ]).
How to Make an Indexer
Defining an indexer is almost like a cross between overloading an operator and a property. It is pretty
easy to do, so we’ll just start with some code that does this. This code could be added as a member of the
Vector class that we made in the last chapter, though this same setup works in any class as well.
public double this[int index]
{
get
{
if(index == 0) { return X; }
else if(index == 1) { returnY; }
else { throw new IndexOutOfRangeException(); }
}
set
{
if (index == 0) { X = value; }
else if (index == 1) { Y = value; }
else { throw new IndexOutOfRangeException(); }
}
}
224
Chapter 35
Indexers
We first specify the access level of the indexer—public in our case—along with the type that it returns.
(Note that we don’t have to use public and static, unlike overloading other operators.) We then use the
this keyword, and the square brackets ([ and ]), which indicate indexing. Inside of the brackets we list the
type and name of the indexing variable that we’ll use inside of this indexer.
Then, like a property, we have a get and a set block. Note that we do not need to have both of these if we
don’t want. We can get away with just one. Inside of the get and set blocks, we can use our index
variable, and like properties, we can use the value keyword in the setter to refer to the value that is being
assigned.
In this example, I’m making it so that people can refer to the x and y components of the vector using the 0
and 1 index respectively. With this in place, we would now be able to do this:
Vector v = new Vector(5, 2);
double xComponent = v[0]; // Use indexing to set the x variable.
double yComponent = v[1]; // Use indexing to set the y variable.
This is much clearer than if we had been forced to do it with methods. (xComponent = v.GetIndex(0);)
Using Other Types as an Index
We’re not stuck with just using ints as an index. We can use any type we want. For example, strings:
public double this[string component]
{
get
{
if (component == "x") { return X; }
if (component == "y") { return Y; }
throw new IndexOutOfRangeException();
}
set
{
if (component == "x") { X = value; }
if (component == "y") { Y = value; }
throw new IndexOutOfRangeException();
}
}
This code is very similar to what we just saw, except that we’re using a string for indexing. If they ask for
“x”, we return the x-component. If they ask for “y”, we return the y-component.
So now we’d be able to do this:
Vector v = new Vector(5, 2);
double xComponent = v["x"]; // Indexing operator with strings.
double yComponent = v["y"];
There’s still more! We can do indexing with multiple indices. Adding in multiple indices is as simple as
listing all of them inside of the square brackets when you define your indexer:
public double this[string component, int index]
{
get
{
return 0; // Do some work here to return a value from the two indices 'component' and 'index'.
}
set
{
// Do the logic to assign the value passed in. `value` contains the value being set.
components.Find(component).AtIndex(index) = value;
Index Initializer Syntax
225
}
}
Indexers can be very powerful, and allow us to make indexing or data access look more natural when
working with our own custom made types. Like with operator overloading, we should take advantage of it
when it makes sense, but be cautious about overusing it.
Try It Out!
Creating a Dictionary. Create a class that is a dictionary, storing words (as a string) and their
definition (also as a string). Use an indexer to allow users of the dictionary to add, modify, and
retrieve definitions for words.
You should be able to add a word like this: dictionary["apple"] = "A particularly delicious
pomaceous fruit of the genus Malus.";
You should be able to change a definition by reusing the same “key” word: dictionary["apple"] = "A
fruit of the genus Malus that often times rots and is no longer delicious.";
You should also be able to retrieve a definition using the indexer: string definitionOfApple =
dictionary["apple"];
Note that the .NET Platform already defines a Dictionary class, which uses generics and in the real
world could be used to do what we’re trying to do here, plus a whole lot more. But we’re trying to get
the hang of indexers here, so don’t use that class while doing this challenge.
Index Initializer Syntax
We’ve talked throughout this book about a lot of ways to initialize things. In Chapter 18 we introduced the
simple constructor, where you pass in parameters to get things set up (new Vector(10, -5)), there’s object
initializer syntax (Chapter 19) that additionally lets you assign values to the object’s properties on the
same line at the same time (new Vector () { X = 10, Y = -5 }), there’s collection initializer syntax that is
used for setting up an array (Chapter 13) or a collection (Chapter 25) (new Vector[] { new Vector(0, 0),
new Vector(10, 5), new Vector(-2, -8) }) and now we’ll introduce one final option.
If a class defines an indexer, you can use index initializer syntax (or an index initializer). Going off of the Try
It Out! problem above, with your own custom dictionary class, you could fill it up like this:
Dictionary dictionary = new Dictionary()
{
["apple"] = "A particularly delicious pomaceous fruit of the genus Malus.",
["broccoli"] = "The 7th most flavorless vegetable on the planet."
// ...
};
This gets translated into direct use of the indexer, so the above code could be written like the code below
without using index initializer syntax:
Dictionary dictionary = new Dictionary();
dictionary["apple"] = "A particularly delicious pomaceous fruit of the genus Malus.";
dictionary["broccoli"] = "The 7th most flavorless vegetable on the planet."
// ...
Because index initializer syntax can be used any time the type defines an indexer, and because lots of
classes define indexers, there are quite a few places this can be used. Sometimes index initializer syntax is
more readable, while other times it’s not. Choose the option that is most readable.
36
36 Extension Methods
In a Nutshell




Extension methods let you define a method that feels like it belongs to a class that you don’t
have control over.
Extension methods are defined as static classes as static methods. The first parameter of the
method is the type of the class that you want to add the extension method to, and it must be
marked with the this keyword: public static class StringExtensions { public static string
ToRandomCase(this string text) { /* Implementation here... */ } }
Once an extension method has been created, you can call it as though it is a part of the class:
string text = "Hello World!"; randomCaseText = text.ToRandomCase();
Extension methods are syntactic sugar to make your code look cleaner. The C# compiler
rewrites into a direct call of the static method.
Let’s say that you are using a class that someone else made. Perhaps one of the classes that comes with
the .NET Platform, like the string type.
What if you’re using that type, and you wish it had a method that it doesn’t have? Particularly, if you don’t
have the ability to modify it? If it’s one of your own classes, you can simply add it in. But if it’s not one that
you can modify, what then?
The string type has a ToUpperCase() method and a ToLowerCase() method, but what if we want to
create a method to convert it to a random case, so each letter is randomly chosen to be upper case or
lower case? (SomEtHIng LiKE tHiS.) Writing the method is relatively easy, but wouldn’t it be nice if we
could make it so that we can say something like myString.ToRandomCase() just like we can do with
myString.ToUpperCase();? Without having access to the class, we wouldn’t normally have the ability to
add our ToRandomCase() method as a new method in the class.
Normally.
But there’s a way in C#. It is called an extension method. Basically, we’ll create a static method in a static
class, along with the this keyword, and we can make a method that appears as though it were a member
of the original class, even though it’s technically not.
Creating an Extension Method
227
Creating an Extension Method
Creating an extension method is as simple as I just described; we’ll make a static class, and put a static
method in it that does what we want for the extension method.
We start by adding a new class file to your project (see Chapter 18). While it is not required, I typically call
my class something like StringExtensions if I’m creating extension methods for the string class, or
PointExtensions, if I’m creating extension methods for a Point class.
In addition, we want to stick the static keyword on our class, to make the whole class a static class. To
start, our class will look something like this:
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
namespace ExtensionMethods
{
public static class StringExtensions
{
}
}
To define our extension method, we simply create a static method here. The first parameter must be of
the type that we’re creating the extension method for (string, in our case), marked with the this keyword:
public static string ToRandomCase(this string text)
{
// The method implementation will go here in a second...
}
We can indicate a return type, and in addition to the first parameter that is marked with the this keyword,
we could also have any other parameters we want.
Like with operator overloading and indexers, this is basically just syntactic sugar. The C# compiler will
rework any place that we call our extension method. So when we’re done, we’ll be able to say:
string title = "Hello World!"
string randomCaseTitle = title.ToRandomCase();
But the compiler will rework the code above to look like this:
string title = "HelloWorld";
string randomCaseTitle = StringExtensions.ToRandomCase(title);
The extension method looks nicer and it feels like a real method of the string class, which is a nice thing.
But this can be a double-edged sword. The method feels like it is a part of the original class, but officially,
it’s not. For instance, you may move from one project to another, only to discover that what you thought
was a part of the original class turned out to be an extension method written by someone else, and you
can no longer use it.
Also, if your extension method is in a different namespace than the original class, you may have problems
where the actual type is recognized, but the extension method can’t be found. To get all of the pieces to
come together, you may need to add in multiple using directives (Chapter 27).
Just be aware of the limitations an extension method has.
We can now finish up our example by completing the body of the ToRandomCase method:
228
Chapter 36
Extension Methods
string result = "";
for (int index = 0; index < text.Length; index++)
{
if (random.Next(2) == 0) // We still need to create the random object.
result += text.Substring(index, 1).ToUpper();
else
result += text.Substring(index, 1).ToLower();
}
return result;
This goes through the original string one character at a time, chooses a random number (0 or 1), and if it
is 0, it makes it upper case. If it is 1, it makes it lower case. So we end up with a random collection of
upper and lower case letters, giving us the desired result.
So our complete code for the extension method class is this:
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
namespace ExtensionMethods
{
public static class StringExtensions
{
private static Random random = new Random();
public static string ToRandomCase(this string text)
{
string result = "";
for (int index = 0; index < text.Length; index++)
{
if (random.Next(2) == 0)
result += text.Substring(index, 1).ToUpper();
else
result += text.Substring(index, 1).ToLower();
}
return result;
}
}
}
As we mentioned earlier, if your program is aware of the extension method, you can now do this:
string message = "I'm sorry, Dave. I'm afraid I can't do that.";
Console.WriteLine(message.ToRandomCase());
We can now use the extension method as though it is a part of the original type.
Try It Out!
Word Count. Create an extension method for the string class that counts the total number of words
in the string. You can make use of the Split method, which works like this: text.Split(' ');. This returns
an array of strings, split up into pieces using the character passed in as the split point.
For bonus points, take this a step further and split on all whitespace characters, including space (' '),
the newline character ('\n'), the carriage return character ('\r'), the tab character ('\t'). For even more
bonus points, ensure that words of length 0, don’t get counted.
Creating an Extension Method
229
Try It Out!
Sentence and Paragraph Count. Following the example of the Word Count problem above, create
additional extension methods to count the number of sentences and paragraphs in a string. You can
assume that a sentence is delimited (ended/separated) by the period (’. ’) symbol, and paragraphs are
delimited with the carriage return symbol ('\n').
For tons of bonus points, put together a simple program that will read in a text file (see Chapter 29)
and print out the number of words, sentences, and paragraphs the file contains.
Try It Out!
Lines of Code. Going even further, let’s make a program that will count the number of lines of code a
program has.
It is often interesting to know how big a particular program is. One way to measure this is in the total
lines of code it contains. (There is some debate about how useful this really is, but it is always fun to
know and watch as your program grows larger.)
Create a simple program that, given a particular file, counts the number of lines of code it has. For
bonus points, ignore blank lines.
If you’re up for a real big challenge, modify your program to start with a particular directory and
search it for all source code files (*.cs) and add them all up to see how big an entire project is.
37
37 Lambda Expressions
In a Nutshell








Lambda expressions are methods that appear “in line” and do not have a name.
Lambda expressions have different syntax than normal methods, which for simple lambda
expressions makes it very readable. The expression: x => x < 5 is the equivalent of the
method bool AMethod(int x) { return x < 5; }.
Multiple parameters can be used: (x, y) => x * x + y * y
As can zero parameters: () => Console.WriteLine("Hello World!")
The C# compiler can typically infer the types of the variables in use, but if not, you can
explicitly provide those types: (int x) => x < 5.
If you want more than one expression, you can use a statement lambda instead, which has
syntax that looks more like a method: x => { bool lessThan5 = x < 5; return lessThan5; }
Lambda expressions can use variables that are in scope at the place where they are defined.
Expression syntax can be used to define normal, named methods, properties, indexers, and
operators as well: bool AMethod(int x) => x < 5;
The Motivation for Lambda Expressions
Lambda expressions are a relatively simple concept. The trick to understanding lambda expressions is in
understanding what they’re actually good for. So that’s where we’re going to start our discussion.
For this discussion, let’s say you had the following list of numbers:
// Collection initializer syntax (see Chapter 25).
List<int> numbers = new List<int>(){ 1, 7, 4, 2, 5, 3, 9, 8, 6 };
Let’s also say that somewhere in your code, you want to filter out some of them. Perhaps you want only
even numbers. How do you do that?
The Basic Approach
Knowing what we learned way back in some of the early chapters about methods and looping, perhaps
we could create something like this:
The Motivation for Lambda Expressions
231
public static List<int> FindEvenNumbers(List<int> numbers)
{
List<int> onlyEvens = new List<int>();
foreach(int number in numbers)
{
if(number % 2 == 0) // checks if it is even using mod operator
onlyEvens.Add(number);
}
return onlyEvens;
}
We could then call that method and get back our list of even numbers. But that’s a lot of work for a single
method that may only ever be used once.
The Delegate Approach
Fast forward to Chapter 32, where we learned about delegates. For this particular task, delegates will
actually be able to go a long way towards helping us.
As it so happens, there’s a method called Where that is a part of the List class (actually, it is an extension
method) that uses a delegate. Using the Where method looks like this:
IEnumerable<int> evenNumbers = numbers.Where(MethodMatchingTheFuncDelegate);
The Func delegate that the Where method uses is generic, but in this specific case, must return the type
bool, and have a single parameter that is the same type that the List contains (int, in this example). The
Where method goes through each element in the array and calls the delegate for each item. If the
delegate returns true for the item, it is included in the results, otherwise it isn’t.
Let me show you what I mean with an example. Instead of our first approach, we could write a simple
method that determines if a number is even or not:
public static bool IsEven(int number)
{
return (number % 2 == 0);
}
This method matches the requirements of the delegate the Where method uses in this case (returns
bool, with exactly one parameter of type int).
IEnumerable<int> evenNumbers = numbers.Where(IsEven);
That’s pretty readable and fairly easy to understand, as long as you know how delegates work. But let’s
take another look at this.
Anonymous Methods
While what we’ve done with the delegate approach is a big improvement over crafting our own method to
do all of the work, it has two small problems. First, a lot of times that we do something like this, the
method is only ever used once. It seems like overkill to go to all of the trouble of creating a whole method
to do this, especially since it starts to clutter the namespace. We can no longer use the name IsEven for
anything else within the class. That may not be a problem, but it might.
Second, and perhaps more important, that method is located somewhere else in the source code. It may
be elsewhere in the file, or even in a completely different file. This separation makes it a bit harder to truly
understand what’s going on when you look at the source code. It our current case, this is mostly solved by
calling the method something intelligent (IsEven) but you don’t always get so lucky.
232
Chapter 37
Lambda Expressions
This issue is common enough that back in C# 2.0, they added a feature called anonymous methods to deal
with it. Anonymous methods allow you to define a method “in line,” without a name.
I’m not going to go into a whole lot of detail about anonymous methods here, because lambda
expressions mostly replaced them.
To accomplish what we were trying to do with an anonymous method, instead of creating a whole
method named IsEven, we could do the following:
numbers.Where(delegate(int number) { return (number % 2 == 0); });
If you take a look at that, you can see that we’re basically taking the old IsEven method and sticking it in
here, “in line.”
This solves our two problems. We no longer have a named method floating around filling up our
namespace, and the code that does the work is now at the same place as the code that needs the work.
I know, I know. You’re probably saying, “But that code is not very readable! Everything’s just smashed
together!” And you’re right. Anonymous methods solved some problems, while introducing others. You
would have to decide which set of problems works best for you, depending on your specific case.
But this finally brings us to lambda expressions.
Lambda Expressions
Basically, a lambda expression is simply a method. More specifically, it is an anonymous method that is
written in a different form that (theoretically) makes it a lot more readable. Lambda expressions were new
in C# 3.0.
In Depth
The Name “Lambda.” The name “lambda” comes from lambda calculus, which is the mathematical
basis for programming languages. It is basically the programming language people used before there
were computers at all. (Which is kind of strange to think about.) “Lambda” would really be spelled
with the Greek letter lambda (λ) but the keyboard doesn’t have it, so we just use “lambda.”
Creating a lambda expression is quite simple. Returning to the IsEven problem from earlier, if we want to
create a lambda expression to determine if a variable was even or odd, we would write the following:
x => x % 2 == 0
The lambda operator (=>) is read as “goes to” or “arrow.” (So, to read this line out loud, you would say “x
goes to x mod 2 equals 0” or “x arrow x mod 2 equals 0.”) The lambda expression is basically saying to
take the input value, x, and mod it with 2 and check the result against 0.
You may also notice with a lambda expression, we didn’t use return. The code on the right side of the =>
operator must be an expression, which evaluates to a single value. That value is returned, and its type
becomes the return type of the lambda expression.
This version is the equivalent of all of the other versions of IsEven that we wrote earlier in this chapter.
Speaking of that earlier code, this is how we might use this along with everything else:
IEnumerable<int> evens = numbers.Where(x => x % 2 == 0);
It may take a little getting used to, but generally speaking it is much easier to read and understand than
the other techniques that we used earlier.
Multiple and Zero Parameters
233
Multiple and Zero Parameters
Lambda expressions can have more than one parameter. To use more than one parameter, you simply
list them in parentheses, separated by commas:
(x, y) => x * x + y * y
The parentheses are optional with one parameter, so in the earlier example, I’ve left them off.
This example above could have been written instead as a method like the following:
public int HypoteneuseSquared(int x, int y)
{
return x * x + y * y;
}
Along the same lines, you can also have a lambda expression that has no parameters:
() => Console.WriteLine("Hello World!")
Type Inference Failures and Explicit Types
The C# compiler’s type inference is smart enough to look at most lambda expressions and figure out what
variable types and return type you are working with, but in some cases, the type inference fails, and you
have to fall back to explicitly stating the types in use, or the code won’t compile.
If this happens, you’ll need to explicitly put in the type of the variable, like this:
(int x) => x % 2 == 0;
Using explicit types in your lambda expressions is always an option, not just when the compiler can’t infer
the type. Most C# programmers will generally take advantage of type inference when possible in a
lambda, but if you like the syntax better or if it makes some specific situation clearer, feel free to use a
named type instead of just using type inference, even if it isn’t required.
Statement Lambdas
As you’ve seen by now, most methods are more than one line long. While lambda expressions are
particularly well suited for very short, single line methods, there will be times that you’ll want a lambda
expression that is more than one line long. This complicates things a little bit, because now you’ll need to
add in semicolons, curly braces, and a return statement, but it can still be done:
(int x) => { bool isEven = x % 2 == 0; return isEven; }
The form we were using earlier is called an expression lambda, because it had only one expression in it.
This new form is called a statement lambda. As a statement lambda gets longer, you should probably
consider pulling it out into its own method.
Scope in Lambda Expressions
From what we’ve seen so far, lambda expressions have basically behaved like a normal method, only
embedded in the code and with a different, cleaner syntax. But now I’m going to show you something that
will throw you for a loop.
Inside of a lambda expression, you can access the variables that were in scope at the location of the
lambda expression. Take the following code, for example:
234
Chapter 37
Lambda Expressions
int cutoffPoint = 5;
List<int> numbers = new List<int>(){ 1, 7, 4, 2, 5, 3, 9, 8, 6 };
IEnumerable<int> numbersLessThanCutoff = numbers.Where(x => x < cutoffPoint);
If our lambda expression had been turned into a method, we wouldn’t have access to that cutoffPoint
variable. (Unless we supplied it as a parameter.) This actually adds a ton of power to the way lambda
expressions can work, so it is good to know about.
(For what it’s worth, anonymous methods have the same feature.)
Expression-Bodied Members
Lambda expressions were introduced to C# in version 3.0, and as I mentioned earlier, one of the big
draws to it is that the syntax is much more concise. That’s great for short methods that would otherwise
require a lot of overhead to define.
C# 6.0 extends this a little, allowing you to use the same expression syntax to define normal non-lambda
methods within a class. For example, consider the method below:
public int ComputeSquare(int value)
{
return value * value;
}
Now that we know about lambda expressions and the syntax that goes with them, it makes sense to point
out that this method could also be implemented with the same expression syntax:
public int ComputeSquare(int value) => value * value;
This only works if the method can be turned into a single expression. In other words, we can use the
expression lambda syntax, but not the statement lambda syntax. If we need a statement lambda, we
would just write a normal method.
This syntax is not just limited to methods. Any method-like member of a type can use the same syntax. So
that includes indexers, operator overloads, and properties (though this only applies to read-only
properties where your expression defines the getter and the property has no setter). The following simple
class shows all four of these in operation:
public class SomeSortOfClass
{
// These two private instance variables are used by the methods below.
private int x;
private int[] internalNumbers = new int[] { 1, 2, 3 };
// Property (read-only, no setter allowed)
public int X => x;
// Operator overload
public static int operator +(SomeSortOfClass a, SomeSortOfClass b) => a.X + b.X;
// Indexer
public int this[int index] => internalNumbers[index];
// Normal method
public int ComputeSquare(int value) => value * value;
}
.
Lambdas vs. Local Functions
235
Lambdas vs. Local Functions
In all cases where you might use a lambda, you could also use a local function, which was introduced in
Chapter 28. The scenarios in which you might use a lambda are also good fits for local functions, and the
two can even be combined together, using an expression-bodied local function. To illustrate, consider the
following three methods which are all equivalent in terms of functionality:
public static IEnumerable<int> FindEvenNumbers1(List<int> numbers)
{
return numbers.Where(x => x % 2 == 0); // Plain lambda expression.
}
public static IEnumerable<int> FindEvenNumbers2(List<int> numbers)
{
bool IsEven(int number) // Local function.
{
return number % 2 == 0;
}
return numbers.Where(IsEven);
}
public static IEnumerable<int> FindEvenNumbers3(List<int> numbers)
{
bool IsEven(int number) => number % 2 == 0; // Expression-bodied local function.
return numbers.Where(IsEven);
}
Each of the three above options are functionally equivalent, but with rather different syntax. The first is a
plain lambda expression. This is probably the most concise of the three, and for somebody comfortable
with lambda expressions, is quite readable.
The second is a local function. It isn’t nearly as concise, but has the advantage of giving a name to the
functionality.
The third is a local function with an expression body. This is something of a compromise of the two.
Each of the above can be the best option in different scenarios. All have their place. Pick the one that
produces the most readable code for any given situation.
Try It Out!
Lambda Expressions Quiz. Answer the following questions to check your understanding. When
you’re done, check your answers against the ones below. If you missed something, go back and
review the section that talks about it.
1.
2.
3.
4.
5.
6.
True/False. Lambda expressions are a special type of method.
True/False. A lambda expression can be given a name.
What operator is used in lambda expressions?
Convert the following to a lambda expression: bool IsNegative(int x) { return x < 0; }
True/False. Lambda expressions can only have one parameter.
True/False. Lambda expressions have access to the local variables in the method they
appear in.
Answers: (1) True. (2) False. (3) Lambda operator (=>). (4) x => x < 0. (5) False. (6) True.
38
38 Query Expressions
In a Nutshell



Query expressions are a special type of statement in C# that allows you to extract certain
pieces from a collection of data and return it in a specific format or organization.
Query expressions are made of multiple clauses:
 A from clause indicates the collection that is being queried.
 A select clause indicates the specific data that is to be returned.
 A where clause performs filtering in the query.
 A let clause allows you to give a name to a part of a query for later reference.
 A join clause allows you to combine multiple collections together.
 An orderby clause allows you to choose the order of the results.
 A group clause allows you to group or partition a set of data into related chunks.
 An into clause continues the query when it otherwise would have terminated.
All queries can be done using query syntax or with normal method calls instead.
A common programming task that you’ll have is one where you must take a collection of data, extract
certain parts, and return it in a certain shape or organized in a specific way. This data extraction and
transformation is often called a query.
Here are some sample queries:



In a real estate system, you might need to find houses that cost less than $300,000 but that also
have at least two bathrooms and at least three bedrooms.
In a project management tool, you might need to search through all tasks in a certain project to
find ones assigned to people who are on a specific team.
In a video game, you might need to find all objects in the game world within a certain distance of
an explosion that can take damage.
LINQ Queries and Declarative Programming
The syntax of C#’s query expressions is inspired by SQL, which is a database query language. C#’s query
syntax is called a Language Integrated Query, or LINQ for short. It’s actually quite different from a lot of the
rest of C#’s syntax. Nine keywords are used for LINQ queries that aren’t used elsewhere.
Lambdas vs. Local Functions
237
But LINQ queries are not complicated. Indeed, the syntax makes queries easy to read and understand.
The primary difference between query expressions and other C# code is that query expressions are
declarative, rather than procedural. With the procedural programming that we’ve gotten used to, we
specify step-by-step how something should be done. With declarative programming, we don’t specify
how—we simply state (“declare”) what we want and let the computer figure out how it gets it on its own.
Anything we might want to do with a query expression could have also been done procedurally with loops
and if statements. But query expressions are often far more readable than their procedural counterparts.
IEnumerable<T> is the Starting Point for Queries
All LINQ queries operate on collections of data. It’s important to point out that when we say “collections”
here, we’re referring specifically to the IEnumerable<T> interface that was introduced in Chapter 25.
Virtually all container types implement this interface, making it so that query expressions can operate on
virtually any data collection. This includes arrays, List<T>, even Dictionary<K, V>, and many others.
For the purposes of this chapter, when I say “collection” or “sequence,” I'm referring to any type that
implements IEnumerable<T>.
Query expressions always produce an IEnumerable<T> as output. If you store the results of a query
expression in a variable, it will need to be of type IEnumerable<T>.
In the event that you need the results in an array or a List<T>, the IEnumerable<T> interface has a
couple of extension methods called ToArray() and ToList() that will convert it to a T[ ] or a List<T>
respectively.
The Structure of a LINQ Query
A query expression is composed of multiple clauses. A clause is a chunk of syntax smaller than a full
expression that has meaning, but requires other clauses around it to be complete.
The structure of a LINQ query is this:




All LINQ queries start with a from clause.
This is followed by any number of where, join, orderby, let, and additional from clauses.
A LINQ query terminates with either a select clause or a group clause.
A LINQ query that would otherwise be terminated can be continued with an into clause, allowing
you to go back to the beginning.
We’ll get into the specifics of each of these clause types through the rest of this chapter.
Sample Classes
Before we dive in, it’s worth defining a few sample classes to use with our queries. The following three
classes give us some sample data to play with through this chapter. You might find some classes like this
in a video game. The GameObject class might serve as a base class for other specialized objects to derive
from (like the stubbed-out Ship class), while the Player class defines some basic properties of a player in
a multi-player game.
public class GameObject
{
public int ID { get; set; }
public double X { get; set; }
public double Y { get; set; }
public double MaxHP { get; set; }
public double CurrentHP { get; set; }
public int PlayerID { get; set; }
}
238
Chapter 38
Query Expressions
public class Ship : GameObject { }
public class Player
{
public int ID { get; set; }
public string UserName { get; set; }
public string TeamColor { get; set; } // A Color class might be better than string here.
}
As you explore the topics in this chapter, you might find it useful to recreate these classes in your own
project. Create a List<GameObject> and a List<Player> and fill the lists with some sample data. This will
give you something to run queries on to compare and explore the results. The following might serve as a
starting point for that:
List<GameObject> gameObjects = new
gameObjects.Add(new Ship { ID = 1,
gameObjects.Add(new Ship { ID = 2,
gameObjects.Add(new Ship { ID = 3,
List<GameObject>();
X = 0, Y = 0, CurrentHP = 50, MaxHP = 100, PlayerID = 1 });
X = 4, Y = 2, CurrentHP = 75, MaxHP = 100, PlayerID = 1 });
X = 9, Y = 3, CurrentHP = 0, MaxHP = 100, PlayerID = 2 });
List<Player> players = new List<Player>();
players.Add(new Player { ID = 1, UserName = "Player 1", TeamColor = "Red" });
players.Add(new Player { ID = 2, UserName = "Player 2", TeamColor = "Blue" });
From Clauses
All queries start with a from clause. A from clause is a generator of sorts, which creates a range variable
over the elements in a sequence (any IEnumerable).
There's nothing too exciting about them, other than defining a source of information to query.
A from clause is always used to start a query expression and can be used in the middle of a query to
introduce additional sources of information. But a lonely from clause doesn’t form a complete query
expression. The following doesn’t compile, but it serves the purpose of illustrating the structure of a
simple from clause:
from o in gameObjects
A from clause introduces a range variable. This is a local variable that can be accessed throughout the rest
of the query. In many ways, a range variable is like the variable in a foreach loop. The variable will take on
each value in the sequence, one at a time, as the query is evaluated.
A from clause is composed of the from keyword, followed by the name of the range variable, followed by
the in keyword, and then finally the sequence that the range variable belongs to—the data set that will be
looked at in this query.
In this from clause, the range variable will have the same type as the collection. So if gameObjects is a
collection of GameObject, then the range variable o will also be of type GameObject.
There is another variation for a from clause where you can specify the type of the range variable like this:
from Ship s in gameObjects
This type can be a more base type or a more derived type as the collection itself. So for example, we could
have done from object o in gameObjects. That would simply mean we don't care about any
GameObject specific code, and are OK falling back to a base class.
But as you can see in the example above, we can also specify a more derived type; we've used Ship
instead of GameObject. This will actually work correctly up until the point where the execution of the
Select Clauses
239
query encounters something that isn't a Ship. So even if gameObjects is a List<GameObject> or an
IEnumerable<GameObject>, this code only has problems if it encounters something that isn’t a Ship.
A from clause gets the query expression started, but isn't a full query expression on its own. So let's
continue on to the select clause, and start putting together full query expressions.
Select Clauses
A select clause is one of two ways to end a query expression (the other being a group clause). A select
clause identifies the portion of an object (including the whole object) to be produced as the final results of
a query expression.
A minimal (but rather useless) complete query expression combining our from clause with a simple
select clause might look like this:
IEnumerable<GameObject> allObjects = from o in gameObjects
select o;
This is about as simple as a query expression gets, resulting in our full gameObjects collection,
completely unchanged. So let’s do another example that is a bit more exciting:
IEnumerable<string> results = from o in gameObjects
select o.CurrentHP + "/" + o.MaxHP;
A select clause is a powerful tool on its own, allowing you to produce new values from existing ones.
Where Clauses
While we're able to make query expressions with just from and select clauses, another key type of clause
is the where clause. A where clause is used to filter elements in a collection, based on some condition.
Consider the where clause we've introduced below, which makes it so we filter to only objects that are
“alive” (their CurrentHP is greater than 0):
IEnumerable<GameObject> aliveObjects = from o in gameObjects
where o.CurrentHP > 0
select o;
A query statement must start with a from clause and must end with a select clause (or a group clause)
but in the middle, you can have any number of where clauses.
Multiple From Clauses
A query statement must start with a from clause, but is also allowed to contain additional from clauses
anywhere in the middle as well. The effect of this is similar to two nested foreach loops and essentially
produces a sub-query within the main query.
Consider the code below:
IEnumerable<string> intersections = from o1 in gameObjects
from o2 in gameObjects
where (o1.Intersects(o2)) // Assumes we have an Intersects method.
select $"{o1.ID} intersects {o2.ID}.";
The where clause will be performed for each pairing of o1 with each o2.
Multiple from clauses can use the same collection again, or pull in a second collection.
240
Chapter 38
Query Expressions
Let Clauses
A let clause is like a select clause in that it defines a new range variable, but does so by producing it from
previous range variables. This makes it a “derived” range variable, computed from other range variables.
The primary purpose of a let clause is to allow you to define something that is used in multiple places
later on in the query expression, or to give a name to a complex operation to make the code more
readable. This can be used later in the query expression. For example:
IEnumerable<string> statuses = from o in gameObjects
let percentHealth = Math.Round(o.CurrentHP / o.MaxHP * 100)
select $"{o.ID} is at {percentHealth}%.";
Join Clauses
The join clause is a powerful tool that will allow us to combine two collections together, pairing them up
based on a rule provided by the programmer.
The sample classes we defined earlier included a gameObjects and a players collection, which we can
join together. The game objects themselves know the ID of the player they belong to, but not any other
player specific data like the user name or color for a specific player. At some point, we might want to
combine our gameObjects collection with our players collection, and a join clause does just that:
var objectColors = from o in gameObjects
join p in players on o.PlayerID equals p.ID
select new { o.ID, p.TeamColor };
A join clause is one of the more complex clauses in the LINQ world, but also one of the more useful ones.
A join clause is also a prime place to use an anonymous type, which we first looked at in Chapter 19. The
example above is a good example of when an anonymous type might be useful. If these results don’t have
to be returned from the method, it is simple enough to just work with it as an anonymous type.
A join clause has some similarities with a from clause. It also introduces a range variable. But contrasted
with using a second join clause, which simply produces all pairings of every item in one collection with
every item in the other, a join will only produce the pairs that meet some criteria, specified by the on X
equals Y portion of the join clause.
In order for a join to work at all, there must be some part of the two sequences that are equal to each
other. (Note that the equals keyword translates to the == operator. If you have overloaded == for a given
type, then you can use that type in a join clause.) You can’t use another comparison operator like < or >=.
The placement of the two sides of the equals keyword is critical. The left side of the equals must
reference an earlier range variable, while the right side may only reference the new range variable.
This is what database people call an inner join. In order for an object in either collection to show up in the
results, there must be a match found for it in the other collection. If there is no player found for a specific
game object, the game object will not be found in the resulting collection. Likewise, if a player has no
corresponding game objects, you won’t see the player in the resulting collection either.
Orderby Clauses
An orderby clause takes the given sequence and produces a new sequence that has been ordered in a
particular way. For example, you could sort all Player objects using a simple orderby clause like this:
Group Clauses
241
IEnumerable<Player> sortedPlayers = from p in players
orderby p.UserName
select p;
The default sort order is ascending order, though the ordering can be specified with either the ascending
or descending keyword at the end of the clause.
IEnumerable<Player> sortedPlayers = from p in players
orderby p.UserName descending
select p;
Note that you don't have to just stick with the name of a property for an orderby clause. You can sort on
the whole object (which makes more sense you’re using a type that has a natural sorting order like int or
string) or you can put in a more complicated expression in there as well.
You can also perform multi-level sorts by separating additional sort criteria with commas. For example:
IEnumerable<GameObject> sortedGameObjects = from o in gameObjects
orderby o.PlayerID ascending, o.MaxHP descending
select o;
This will sort by PlayerID first, from lowest to highest, then by MaxHP from highest to lowest.
Group Clauses
A group clause is another extremely powerful clause, though perhaps not quite as common. A group
clause is one that allows you to take a single sequence and bundle the elements into groups based on
criteria you supply. For example, the following query will take all game objects and separate them into
groups based on the PlayerID that each object belongs to:
IEnumerable<IGrouping<int, GameObject>> groups = from o in gameObjects
group o by o.PlayerID;
This is the first query that we’ve seen that doesn't end with a select clause. A group clause is the other
way to terminate a query expression. But you’ll also notice that the variable type that we store these
results in is quite a bit different, and quite a bit more complex than what we've seen before. (This nesting
of generic types is one of the reasons some people like var, because they don't have to type all that out.)
Rather than a simple IEnumerable<SomeType>, our type is IEnumerable<IGrouping<GroupType,
ObjectType>>. The IGrouping<TKey, TElement> interface defines what a group looks like. It is just a
simple collection object that contains the items in the group, and also has a Key property that holds the
group ID. So for example, in the previous code, each group's key would be the player ID that all items in
the group had, and the items in the group would be all of the game objects that belonged to that
particular player.
To illustrate how these groups are structured, the code below prints the groups from the previous sample:
foreach(IGrouping<int, GameObject> group in groups)
{
Console.WriteLine(group.Key);
foreach(GameObject o in group)
Console.WriteLine("
" + o.ID);
}
The results of a group clause is essentially a collection of collections, where each collection can be
identified by its key.
You won’t get empty groups from a simple group statement.
242
Chapter 38
Query Expressions
Into Clauses
While a query expression ends with a select or group clause, it doesn’t actually have to end when you
encounter one. An into clause, sometimes called a continuation clause, allows you to take the results of a
select or group clause and put the value into another range variable. This is a little like a let clause in that
regard. Additional query clauses can follow an into clause:
var objectCountPerPlayer = from o in gameObjects
group o by o.PlayerID
into playerGroup
select new { ID = playerGroup.Key, Count = playerGroup.Count() };
You can see that the into clause above allows us to continue past the group clause and do more work.
In this particular example, because the into follows a group clause, the type will be some sort of
IGrouping. We create another anonymous type that has an ID, based on the player that the group is for,
and a Count property that is the total number of game objects that belong to each player.
An into clause can follow both group and select clauses.
Group Joins
There is one final type of clause, assembled from other clause keywords, that is both powerful and
somewhat convoluted. This is the group join, which is something of a hybrid between a group clause and
a join clause. (Bet you didn’t see that coming!)
Similar to a join clause, a group join combines together two other sequences of data.
A normal join clause will take two collections and pair up elements that belong to each other, as defined
by sharing some key. After the join clause, you are able to access the matching object from each
collection together, at the same time. A caveat to this is that if an object in either sequence didn't match
something in the other, then you will never see it as a result. A group join is a little different in this regard.
Similar to a join, a group join operates on two collections, and objects are matched together. But more
precisely, with a group join, all items in the second collection are bundled together into a group that
belongs to a single item in the first collection. This applies even if there were no items in the second
collection that belonged to an item in the first collection (which results in an empty group).
With a normal join clause, what you have to work with is the two objects that matched. After a group join,
you have the object from the first collection, followed by a (possibly empty) IEnumerable of the items
that belonged to it from the second list.
A group join will make more sense with an actual example, so here is one:
var playerObjects = from p in players
join o in gameObjects on p.ID equals o.PlayerID into objectsOwnedByPlayer
select new { Player = p, Objects = objectsOwnedByPlayer };
foreach(var objects in playerObjects)
{
Console.WriteLine(objects.Player.UserName + " has the following objects:");
foreach(GameObject o in objects.Objects)
Console.WriteLine($"
{o.ID} {o.CurrentHP}/{o.MaxHP}");
}
First thing's first: the syntax. A group join is performed by a somewhat normal join clause, followed
immediately by an into clause.
Query Syntax and Method Call Syntax
243
After the group join, you can access the item from the first collection with the same name as before, and
the matching items from the second collection through the name indicated by the into clause.
The above group join starts with all of the game objects and bundles them into groups based on IDs from
the first collection (players). Each player ends up with a group, even if it is an empty group. (Game objects
that didn’t match with any player are left out of the results.) The code at the end iterates through the
results and prints it out, to show how you might interact with data that comes out of a group join.
Query Syntax and Method Call Syntax
A key component of query expressions is that, in addition to the query syntax that we’ve been learning
here, there is a second syntax for writing these queries. This second syntax is called method call syntax,
and—surprise, surprise—it is done through simple method invocations.
For every type of clause we’ve seen above, there is a method that is available that does the exact same
thing. All of these methods are extension methods (see Chapter 36) on the type IEnumerable<T>.
The following list outlines how the above clauses are turned into the method call syntax:








from clauses turn into the collection object that you want to begin invoking methods on.
select clauses turn into invocations of the Select method.
where clauses turn into invocations of the Where method.
let clauses do not have exact translations.
join clauses turn into invocations of the Join method.
orderby clauses turn into invocations of OrderBy, OrderByDescending, ThenBy, and
ThenByDescending.
group clauses turn into invocations of the GroupBy method.
Group joins turn into invocations of the GroupJoin method.
These methods make heavy use of lambda expressions, which we saw in Chapter 37. As a simple
example, the following code shows the same functionality in both query syntax and method call syntax:
IEnumerable<int> aliveIDs1 = from o in gameObjects
where o.CurrentHP > 0
select o.ID;
IEnumerable<int> aliveIDs2 = gameObjects.Where(o => o.CurrentHP > 0).Select(o => o.ID);
There are quite a few other query-related methods that exist on the IEnumerable<T> interface that have
no query syntax equivalent. We won’t go through those here, but taking a peek at them someday will
probably be worth your time.
Queries are Lazy When Possible
It should be pointed out that queries (both syntaxes) are “lazy,” meaning only the bare minimum is
performed for any given result. For example, consider the following query expression and foreach loop:
IEnumerable<GameObject> aliveObjects = from o in gameObjects
where o.CurrentHP > 0
select o;
foreach(GameObject o in aliveObjects)
Console.WriteLine($"{o.ID} is alive.");
After the query is built and assigned to aliveObjects, but before the foreach, absolutely none of the code
in the query expression has been executed.
244
Chapter 38
Query Expressions
As the foreach loop begins looping over the items in aliveObjects, just enough of the query is executed
to produce the next item.
For example, the first time through the foreach loop, only enough items from the original gameObjects
collection will be looked at to find one where its CurrentHP is more than 0.
Lazy evaluation can be bad or good. On one hand, if you never actually walk through the entire result set,
then the query doesn’t have to process every single item. Since queries can get quite complicated and
data sets can be very large, this can save a lot of time.
On the other hand, if you end up evaluating a query more than once, it can be quite costly. This can be
solved by converting the results to an array or list, using the ToArray() or ToList() methods respectively.
Either of these will cause the query to be evaluated fully once, but then caches the results in the array or
list, preventing you from needing to reevaluate it again the second time.
Try It Out!
Full Health. Based on the GameObject class defined earlier in this chapter, you could say that a
particular GameObject is at full health if its CurrentHP was equal to its MaxHP. Based on this
definition of full health, write query expressions that return the following:
1.
2.
A collection of all game objects that have full health.
A collection of the IDs of all game objects that have full health.
Try It Out!
Percent Health. Write a query to produce game objects grouped by their owner (PlayerID) and
ordered based on the percent health (CurrentHP / MaxHP).
Try It Out!
Back to Procedural Programming. Take the query expression below and rewrite it without using any
LINQ methods or any LINQ keywords, just by using if statements and loops.
IEnumerable<GameObject> aliveObjects = from o in gameObjects
where o.CurrentHP > 0
select o;
Try It Out!
Query Expressions Quiz. Answer the following questions to check your understanding. When you’re
done, check your answers against the ones below. If you missed something, go back and review the
section that talks about it.
1.
2.
3.
4.
5.
What type of clause (or what keyword) is used to start a query expression?
What two types of clauses can terminate a query expression?
What clause is used to filter data?
True/False. You can order by multiple criteria in a single orderby clause.
What clause is used to combine two related sets of data together?
Answers: (1) from clause. (2) select and group. (3) where clause. (4) True. (5) join clause.
39
39 Threads
In a Nutshell






Threading allows you to run sections of code simultaneously.
Starting a new thread: Thread thread = new Thread(MethodNameHere); thread.Start();
In the above code, the method must have a void return type with no parameters.
Alternatively, ParameterizedThreadStart lets you pass in a parameter (object) to the
method.
You can wait for a thread to finish with the Join method: thread.Join();
If you need to worry about thread safety (preventing problems when multiple threads are
modifying the same data), you can use the lock keyword: lock(aPrivateObject) { /* code in
here is thread safe */ }.
Back in the day, all computers had one processor. Nowadays, they usually have a lot more than one.
(More specifically, this is usually done by having multiple cores that are all a part of the same processor
chip.) I’m currently working on a computer with four processors, each of which is “hyper-threaded,”
making it appear that I have a total of eight processors. And this computer isn’t even all that fancy. There
are machines out there that have 16 or 32 processors, or even hundreds or thousands, all working
together. (Turns out, they’re kind of expensive.) You could even set up a program to hand off work to
other computers across the network, giving you an unlimited number of processors at your disposal.
Computers have a lot of power, but unless you intentionally structure your code to run on multiple
processors, it will only ever use one. Think of all of that raw computing power going to waste!
In this chapter, we’re going to take a look at threading. The basic process of threading is that we can take
chunks of code that are independent of each other and make them run in separate threads. A thread is
almost like its own program, in that the computer will run multiple threads all at the same time on
different processors. (For the record, a thread is not its own program. It still shares memory with the
program/process that created it.)
When many threads are running, each processor will run a thread for a while, and then suddenly switch it
out for a different one. The computer gets to decide when it is time to make the switch and which thread
246
Chapter 39
Threads
to switch to, but it does a pretty good job, so that’s one less thing we need to worry about. This switching
is called a context switch.
All threads will be treated fairly equally, but they will get switched around from time to time. It’s
something that you need to be aware of, and even write your code in a way that can handle it. If you fail
to do it correctly, you can end up with strange intermittent errors that only appear to happen on Tuesdays
where you ate tacos for lunch and the moon is waxing. We’ll talk more about dealing with this in the
section about Thread Safety.
There’s a lot that goes into threading, and we simply don’t have enough time to discuss all of the ins and
outs of it here. So we won’t. Instead, we’ll take a look at the basics of threading, and I’ll allow you to dig
further into threading as you need it.
Threading Code Basics
We’ll start out with a very simple example of how to start a new thread. To get started, let’s say we have
some work we want to do on a separate thread. This can really be anything, but let’s say it is this:
public static void CountTo100()
{
for (int index = 0; index < 100; index++)
Console.WriteLine(index + 1);
}
To run this in a separate thread, you will need to create a new thread, tell it what method to run, and then
start it. The following code does this:
Thread thread = new Thread(CountTo100);
thread.Start();
You’ll also need to add a new using directive to get access to the Thread class (using System.Threading;)
like we discussed back in Chapter 27.
The first line creates a new Thread object, but take a look at that constructor. We’ve passed in the method
that we want it to run as a parameter. This is using a delegate. (Another good use of delegates!) This
happens to be the ThreadStart delegate, which has a void return type and no parameters, so the
method we use needs to match that.
After we call the Start method, a new thread will be created and will start executing the method we told it
to run (CountTo100). At that point, we have two threads: the original thread, and the new one that
running in CountTo100.
To have the original thread wait at some point for this new thread to terminate, we use the Join method:
thread.Join();
When a thread runs into this statement, it freezes and waits there until the other thread finishes up,
effectively joining the execution of the two threads.
You can create as many threads as you want. As I mentioned earlier, they’ll all get their fair share of the
total processor time. (Though you don’t want to have too many threads, because it takes time to create
them all, and more threads means more frequent context switches, which take time.)
For example, here is some code that runs two threads and waits for them to finish:
using System;
using System.Threading;
Using ParameterizedThreadStart
247
namespace Threading
{
class Program
{
static void Main(string[] args)
{
Thread thread1 = new Thread(CountTo100);
thread1.Start();
Thread thread2 = new Thread(CountTo100);
thread2.Start();
thread1.Join();
thread2.Join();
Console.ReadKey();
}
public static void CountTo100()
{
for (int index = 0; index < 100; index++)
Console.WriteLine(index + 1);
}
}
}
Try running this code. There are two threads that are both going to print out the numbers 1 through 100.
Typically, you’ll see one thread active for a little while, and then the other active. So you might see the
numbers 1-24 printed out, and then that thread will stop and the other thread will print out, say, 1-52, and
then the first one will print out 25-87, and so on, until they both finish up.
You’ll never get the exact same output the second time around. Things will be a little bit different because
the operating system is doing context switches as it sees fit. That’s an important point to remember about
threads: the timing and ordering is unpredictable. This can be a big pain when you’re trying to debug a
problem in a multi-threaded program, and it is worth considering before you decide to run things on
separate threads.
One other thing that I should point out is that you can make the current thread “sleep” for a certain
amount of time. This is useful when you know one thread needs to wait a while for another thread to do
something. To make the current thread sleep, you can call the static Sleep method in the Thread class:
Thread.Sleep(1000);
The parameter that you pass in is the amount of time to sleep for, measured in milliseconds. So 1000 is 1
second. The code above will cause the current thread to stop execution for one second, while other
threads have a chance to run.
Using ParameterizedThreadStart
In the code we were using above, we used the ThreadStart delegate. This meant the method we ran
could have no parameters, which meant that we couldn’t pass any information to the method we were
calling, which is somewhat limited.
There is an alternative that allows us to use methods that take a single parameter instead. To do this, we
use the ParameterizedThreadStart delegate instead. This delegate has a return type of void and a
single parameter of type object. Because the parameter is of type object, we can basically use it for
anything, as long as we cast it correctly to the right type.
For example, consider this variation of our CountTo100 method, which counts to any number you want:
248
Chapter 39
Threads
public static void CountToNumber(object input)
{
int n = (int)input;
for (int index = 0; index < n; index++)
Console.WriteLine(index + 1);
}
This has a parameter of type object, which we cast to an int, and use through the rest of our method.
Because this method matches the ParameterizedThreadStart delegate, we can create a new thread,
with a reference to this method, and start it with a parameter, which gets passed along into our method:
Thread thread = new Thread(CountToNumber);
thread.Start(50);
Using object allows us to use any type imaginable, as long as we’re willing to cast. It’s unfortunate that
there isn’t a version of ParameterizedThreadStart that uses generics, but there isn’t. Furthermore, while
the ParameterizedThreadStart doesn’t allow you to return information, you could easily construct an
object that has a property that can store what would have been returned.
To illustrate, below is a simple little program that will do division on a separate thread (yeah, it’s overkill):
using System;
using System.Threading;
namespace Threading
{
public class DivisionProblem
{
public double Dividend { get; set; } // the top
public double Divisor { get; set; } // the bottom
public double Quotient { get; set; } // the result (normally would be returned)
}
class Program
{
static void Main(string[] args)
{
Thread thread = new Thread(Divide);
DivisionProblem problem = new DivisionProblem();
problem.Dividend = 8;
problem.Divisor = 2;
thread.Start(problem);
thread.Join();
Console.WriteLine("Result: " + problem.Quotient);
Console.ReadKey();
}
public static void Divide(object input)
{
DivisionProblem problem = (DivisionProblem)input;
problem.Quotient = problem.Dividend / problem.Divisor;
}
}
}
Thread Safety
249
Try It Out!
Frog Racing. Let’s make a little simulator for a frog race. The idea is that there are multiple frogs that
are lined up and competing against each other to jump across a finish line. In order to finish the race,
a frog needs to jump a total of 10 times. Each frog runs on its own thread, and we’ll have three total.
To do this, create a method that matches the ParameterizedThreadStart delegate. As input, the
object that is passed in will be the frog’s number. Inside of that method, use a loop to print out “Frog
#X jumped” and then use Thread.Sleep and a Random object to have the frog/thread sleep for a
random amount of time between 0 and 1 seconds. When the frog/thread has jumped ten times and
the loop ends, print out “Frog #X finished.” (Generating random numbers is discussed in Chapter 16.)
Start the frog race by creating three separate threads and starting them all with different numbers.
Wait for each thread to finish using the Join method.
Thread Safety
We’ve covered the basics of threading, but it is worth looking at another, more advanced threading topic.
Not all situations need to be worried about this, but some do, so it is worth knowing a little about.
In the examples that we’ve done so far, each thread has been working with its own data. But what if there
was something that they had to share? Remember that threads get swapped out whenever the operating
system decides, and a thread may actually be in the middle of something important when that context
switch hits.
To help explain the concept of thread safety, I’m going to draw on a real-world example. Imagine you’re
driving your car down a road that has three lanes. You’re on the outside lane (the black car), and another
car is on the inside lane (the white car):
You want to change lanes to the middle. The normal process is that you look over into that other lane,
make sure it is clear, and if so, you move over. But what if the white car was doing the same thing, without
you knowing it? The other driver looked as well, saw that the lane was clear, and moved over. Unless one
of you realizes the problem, you’re headed for a crash.
Like with the cars, when you have multiple threads that are using any of the same resources, if you aren’t
careful, two threads can run into each other and cause problems.
Imagine even the simple problem of adding one to a variable. To do this, the computer will do the
following steps:



Read the current value from the variable.
Do the math to figure out the result of adding 1 to the value.
Store the new value back in the variable.
Now, imagine that two separate threads are both given the task of adding 1 to the variable. In a normal
“happy” scenario, this variable should get incremented twice.
250
Chapter 39
Threads
But let’s see how things could go bad with multiple threads. Let’s say our variable starts with a value of 1.
In theory, both threads should increment the variable, and it should end up with a value of 3. But the
following could happen as well:






Thread #1 reads the current value from the variable (1).
Thread #1 does the math to figure out the result of adding 1 to the value (2).
Thread #2 reads the current value from the variable (which is still set to 1).
Thread #2 does the math to figure out the result of adding 1 to the value (getting 2 also).
Thread #2 stores the new value back into the variable (2 gets assigned back).
Thread #1 stores the new value back into the variable (once again, 2 is assigned back).
Both threads did the work they were supposed to do, but because the two are running at the same time
(or because of a context switch) things didn’t turn out the way they should, and the variable is left with a
value of 2, instead of 3 like it should be.
This example is kind of a toy problem, but the reality is that any time you have more than one thread
modifying some specific data, you’re likely to run into problems like this.
We want to be able to address this problem and make certain sections of code that we know might be
problematic be thread safe, meaning that the code can prevent bad things like this from happening when
multiple threads want to use it.
If we have a certain block of code that modifies data that could be accessed by other threads, we call that
section a critical section. We will want to make it so that only one thread can get inside it at a time. This
principle of only allowing one thread in at a time is called mutual exclusion and the mechanism that is
used to enforce this is often called a mutex.
To enter a critical section, we require that the thread that is entering the section acquire the mutual
exclusion lock for a specific object. When the thread has this lock, it can enter the critical section and
execute the code. When it’s done, it releases the lock, freeing it up for the next thread. If a thread shows
up and tries to grab the lock that is already taken, the thread will be suspended until the lock is released.
Doing this in code is pretty simple. The first step is to create some sort of object that is accessible to
anything that needs access to the critical section, which can act as the lock. Often, this is best done with a
private instance variable in the same class as the data that is being modified:
private object threadLock = new object(); // Nothing special needed. Any object will do.
You could alternatively use a static variable instead of an instance variable if you need thread safety
across all instances of the class.
To actually make a block of code thread safe, you simply add a block surrounding the critical section with
the lock keyword:
lock(threadLock)
{
// Code in here is now thread safe. Only one thread at a time can be in here.
// Everyone else will have to wait at the "lock" statement.
}
That’s the basics of threading in C#. There is a lot that goes on in multi-threaded applications, and there
are entire books dedicated to the subject. We can only cover the basics here, but it should give you an
idea of the fundamentals.
40
40 Asynchronous Programming
In a Nutshell




Asynchronous programming is where you take a particular task or chunk of code and run it
on a separate thread, outside of the main flow of execution.
C# has used many ways of achieving asynchronous programming in the past.
The Task-based Asynchronous Pattern (TAP) uses the Task and Task<TResult> class to
represent a chunk of work that is running asynchronously.
Tasks can be awaited with the await keyword (if the method is marked with async), allowing
you simple syntax for scheduling additional work that happens once the task has been
completed: HighScores = await highScoreManager.LookupScores();
In this chapter, we’re going to continue what we started in the previous chapter, and introduce the best
way to do most asynchronous things in C#. This uses the async and await keywords that were introduced
into the C# language in C# 5.0.
Asynchronous programming is hard. There’s not enough space in this chapter to cover every single detail
and strange corner case. Rather, the goal of this chapter is to introduce you to the syntax surrounding
calling code asynchronously, and get you to a point where it’s not a deep, dark mystery, but something
you feel comfortable with doing, while leaving some of the finer points of asynchronous programming for
a later point in time.
In this chapter, we’ll start by defining what asynchronous programming is and why you might use it. Then
we’ll take a look at how asynchronous programming has evolved in C# over time. We’ll finally introduce
the Task-based Asynchronous Pattern and the async and await keywords, which serve as the basis of
modern asynchronous programming in C#.
What is Asynchronous Programming?
Asynchronous programming means taking a piece of code and running it on a separate thread, freeing up
the original thread to continue on and do other things while the task completes.
252
Chapter 40
Asynchronous Programming
The typical use case is when you start something that you know is going to take a while to happen. “A
while” is measured in computer time, so we may be talking only a few milliseconds (or not). Some
common scenarios include making a request to a server or a website, making requests to the file system,
while running a particularly large or long running algorithm that you’re using, or any other time where you
know a particular piece of code is going to take some work to get done.
Modern computers have a lot of computational power, and users are expecting responsive user
interfaces. Those two combined mean asynchronous programming is becoming more and more
necessary and important to know.
The opposite of asynchronous programming (sometimes called “asynchrony”) is synchronous
programming, and it’s what we’ve been doing up until now.
Approaches from the Early Days
There have been many approaches to doing asynchronous programming in C#’s past. While no longer
recommended or preferred, those older approaches are worth mention because they illustrate the
complexity of the problem, the beauty of the final solution, and the decade long journey it took to get
there.
Throughout this section, we’ll stick with a recurring example of getting a list of high scores for a game
from a web server. This will help us see the trade-offs of the various approaches that we’ll see.
A Synchronous Approach
Let’s start by looking at how we might do this task (looking up high scores) using a traditional synchronous
approach. That means blocking and waiting for the results to come back before moving on. Obviously, this
defeats the purpose of what this chapter is talking about, but it is a worthwhile exercise anyway. The
biggest thing we’ll gain from this is an example of what “ideal” code looks like here. Synchronous requests
are straightforward, and the method calls are clean and simple.
To do our high score lookup in a synchronous fashion, we might use code like this:
Score[] highScores = highScoreManager.LookupScores();
That’s relatively straightforward. We just call the method directly, which returns the results upon
completion, and we store them in an array.
I’m skipping the actual implementation of the LookupScores method. I know that is going to be a
disappointment to some people, but it’s irrelevant to the current discussion. The point is, it doesn’t matter
what the work is, just that it’s some sort of long running task that we really don’t want to sit around
waiting for. (But if you’re dying of curiosity, one possible way to do this might be with an HTTP request
using C#’s WebClient class, which returns the results in a text stream of some sort, which could then be
parsed and turned into a list of scores.)
While that code is short and easy to understand, it’s not asynchronous.
Creating Threads Directly
Having seen a synchronous version of our high scores lookup problem, let’s turn our attention to doing
this in an asynchronous way.
Our first option is to create a thread like we did in the last chapter. It’s using the most basic building block
(starting a thread directly) but it certainly fits the definition of asynchronous programming.
Based on what we learned before, this might look something like this:
Approaches from the Early Days
253
Thread thread = new Thread(() =>
{
Score[] highScores = highScoreManager.LookupScores();
// Do something with the results.
HighScores = highScores;
});
thread.Start();
That code isn’t so terrible. It gets the job done for sure. But as we’ll soon see, there are better ways of
structuring this code.
Using the ThreadPool
While directly creating a thread was our approach in the last chapter, it has long been discouraged. One
obvious problem with this approach has been that it’s pretty easy to end up with so many threads that
you drown the CPU. You spend far too much of your time switching between threads, and far too little
doing real work.
C# has the concept of a thread “pool”, wrapped up in the ThreadPool class. The idea here is that the .NET
runtime can provide a collection of threads that already exist (you don’t need to create them yourself) that
can then be reused to run any generic task. The ThreadPool class has the smarts to maintain the optimal
number of threads.
When you have a chunk of work that you need done asynchronously, you can just hand it off to the
thread pool. This is done by using the static QueueUserWorkItem method:
ThreadPool.QueueUserWorkItem(data =>
{
Score[] highScores = highScoreManager.LookupScores();
HighScores = highScores;
}, null);
This is pretty close to what we had in the previous iteration. This code uses a lambda statement. We could
have used a named method instead, but this type of thing is a perfect use for lambda statements, so I
went with that approach here in the sample code as well.
This gets us over the problem of not knowing when we should create new threads and when we should
reuse an old thread, and we were also able to ditch the Thread.Start call.
You’ll notice with this approach that the method required a parameter (data), which I filled in with null.
This is more along the lines of the ParameterizedThreadStart that we talked about in the last chapter.
At any rate, this is a bit simpler than what we had before. Using the thread pool is preferable to not using
it, but it’s also still not our final solution. So let’s keep looking.
Event-Based Asynchrony
The idea with the event-based asynchronous approach is that you call a method that is known to take a
long time and it returns immediately. When the method completes asynchronously, an event on the class
is raised.
Using Event-based asynchrony like this, we have to subscribe to the right event, and in most cases, the
asynchronous method will end with the word Async:
HighScoreManager highScoreManager = new HighScoreManager();
highScoreManager.HighScoresLoaded += OnHighScoresLoaded;
highScoreManager.LookupScoresAsync();
And elsewhere we define our event handler method:
254
Chapter 40
Asynchronous Programming
private void OnHighScoresLoaded(Score[] highScores)
{
// Do something with the results.
HighScores = highScores;
}
In the past, this has been a rather common approach to long running tasks. As you explore the .NET
Standard Library that C# offers, you’ll inevitably see methods that end with Async like this, which expect
you to subscribe to a related event, and put your code for dealing with the results in the event handler.
While it works, it’s definitely a little annoying that you’ve now separated your code into two separate
pieces. You have the part that sets up and calls the method, and then in a different location, you have the
method for dealing with the results. You’ll see this same problem in the next few iterations as well.
A second problem we introduce with this is the event subscription. The second time we make this
request, it’s easy to forget that we may already have subscribed to the event (with the +=) and we may
accidentally subscribe again. We can work around that but it does create more things you have to
remember to do as a developer.
The AsyncResult Pattern
Moving along in our tour of asynchrony, our next option is the AsyncResult pattern. This approach is
similar to various fast food restaurants and other places where you make your order or request, and
you’re given a ticket of some sort. When it’s done, you can return the ticket to get your order (the results).
Or you can take your ticket and stand at the counter waiting impatiently.
Using this pattern, you typically start with a synchronous method (let’s call it X). You then add a BeginX
method and EndX method to the mix. The Begin method starts the synchronous method running in an
asynchronous way and returns your “ticket” in the form of an object that implements the IAsyncResult
interface. You can use this object to periodically check if the asynchronous task is completed or not, or
you can call the End method, passing in the IAsyncResult that you got from calling the Begin method,
which will block, and cause the thread to do nothing else until the results come back.
This sometimes shows up in the form of three distinct named methods (X, BeginX, and EndX) but the
easiest way to implement this is by using some functionality on the Delegate class:
HighScoreManager highScoreManager = new HighScoreManager();
Func<Score[]> scoreLookupDelegate = highScoreManager.LookupScores; // Synchronous version.
IAsyncResult asyncResult = scoreLookupDelegate.BeginInvoke(null, null);
HighScores = scoreLookupDelegate.EndInvoke(asyncResult);
In that second line, we store the LookupScores method in a delegate type. Remember from the chapter
on delegates (Chapter 32) that we can usually get away with some variation of Action or Func, and that’s
what we’ve done in this case. (Func<Score[]> is a delegate for any method that has no parameters and
returns an array of Score.)
Once stored in the delegate, we can start off the process by calling BeginInvoke. The two null values are
for a callback method that should be called when the asynchronous task is completed, and a parameter
that can be passed into the callback method.
If the method we were trying to make asynchronous had any parameters, we’d be able to list them in
BeginInvoke before those two parameters.
In the last line of that code, we stop and wait for the task to complete with EndInvoke. That obviously
defeats the purpose of making it asynchronous in the first place, but in a more realistic scenario, you
The Task-based Asynchronous Pattern
255
wouldn’t call EndInvoke immediately. Instead, you’d continue on to other things, and you could use the
IAsyncResult that comes back to keep track of the asynchronous task’s progress.
The best part about this delegate approach is that you can use it on any method. There are no limitations
on it. It’s a pattern that’s infinitely reusable.
Unlike the event-based version, you don’t have to worry about subscribing and unsubscribing correctly.
There’s no way to make that mistake with this approach.
At this point, you might be starting to feel like this async stuff just seems to be getting uglier and uglier,
and more and more complicated. I definitely think that’s the case so far. But let’s keep going. It gets better
from here.
Callback Methods
One of the cleanest ways we could structure an asynchronous call is to have our method run
asynchronously, but include a parameter that allows the programmer to supply a method that will be
called on completion. The method you pass in (in the form of a delegate) is called a callback method.
public void LookupScores(Action<Score[]> callbackMethod)
This can be called like this, using a lambda statement:
highScoreManager.LookupScores(scores => { HighScores = scores; });
We used a lambda here, but it could have been a normal named method as well.
This code is actually fairly readable. It’s not quite as good as the original synchronous version, but it’s the
cleanest version we’ve seen so far. It probably seems strange that LookupScores has a parameter that’s a
delegate, until you realize that this is a callback method. We’re definitely making progress though.
The Task-based Asynchronous Pattern
As you can see, asynchronous programming has been a mess in C# in the past. And it’s not just C#. Every
language and every programmer has struggled with this. Obviously we need a better solution that what
we’ve seen so far.
C# 4.0 introduced yet another pattern for solving this problem: the Task-based Asynchronous Pattern
(TAP). TAP doesn’t magically solve our code clarity problems, but it lays the foundation for what came in
C# 5.0, which does (for the most part).
TAP introduced two new classes: Task and Task<TResult>. Task represents a long running asynchronous
chunk of work, while Task<TResult> is a generic variant that additionally serves as a promise of a
resulting value when the task is completed. Task is used when the asynchronous task doesn’t return a
value or when you don’t care to do anything with the returned value. When you care about the result, you
would use the generic version, which has the option of grabbing the result from the task upon completion
and doing something more with it.
Let’s look at how we’d implement our high score lookup using the TAP approach:
public Task<Score[]> LookupScores()
{
return Task.Run(() => {
// Do the real work here, in a synchronous fashion.
return new Score[10];
});
}
256
Chapter 40
Asynchronous Programming
Instead of directly returning the score array, we return a Task that contains it. More accurately, we return
a task that promises to give us an array of scores when it completes. In this case, because we want to do
something with the result, we want to use the generic version of Task, which promises a resulting value
upon completion.
Outside of this class, we can handle a Task return value in a few different ways. The first way would be to
just ignore it. If it were an asynchronous task that is “fire and forget,” we could just ignore the return
value.
The second option is to take the task and call Wait() on it:
Task<Score[]> scoresTask = highScoreManager.LookupScores();
scoresTask.Wait();
HighScores = scoresTask.Result; // Calling Result will actually also block, as though you had called Wait.
Wait causes the current thread to block, doing nothing else until the task finishes. This isn’t the desired
effect because it forces the main thread to suspend. It’s not happening asynchronously anymore.
Which leads us to the third option: ContinueWith.
Task<Score[]> scoresTask = highScoreManager.LookupScores();
scoresTask.ContinueWith(task => HighScores = task.Result);
ContinueWith specifies a second method (another lambda expression in this case) that should execute
when the task finishes. That code doesn’t happen when the flow of execution reaches that line in the
code. Rather, the continuation is only scheduled then, and executed when the task actually completes at
some later point in time.
The ‘async’ and ‘await’ Keywords
We’re now ready for the latest and greatest in asynchronous programming in C#. Building off of the TAP
pattern, C# 5.0 made this far simpler and easy to read by integrating it into the language itself. It does this
by adding two keywords to the language: async and await.
The Task and Task<TResult> classes still function as the core building block of asynchronous
programming with these new keywords. You’ll still create a new Task or Task<TResult> using Task.Run,
like in the previous section. The real difference happens when you get a Task returned to you from a
method, where you can do something when the task completes or do something with the result the task
gives you upon completion:
Score[] highScores = await highScoreManager.LookupScores();
Before looking at what that code actually does, let’s compare it to the synchronous version, right at the
start of this chapter. Remember this guy?
Score[] highScores = highScoreManager.LookupScores();
It’s surprisingly similar. And yet, it is asynchronous. While previous approaches to asynchronous
programming have been convoluted, using Task or Task<TResult>, combined with the new await
keyword, everything seems to fall in place and we get a clean solution.
So what does that await keyword do exactly?
Well let’s start by rounding that piece of code out a little bit. I’m going to put some Console.WriteLine
statements in there and wrap it in a method:
public async void GrabHighScores()
{
The ‘async’ and ‘await’ Keywords
257
// Preliminary work...
Console.WriteLine("Initializing asynchronous lookup of high scores.");
// Start something asynchronously.
Score[] highScores = await highScoreManager.LookupScores();
// Work to be done after completion of the asynchronous task.
Console.WriteLine("Completed asynchronous lookup of high scores.");
}
Let’s start there in the middle, with the await keyword, since it’s the central point of this whole discussion.
That LookupScores method returns a Task<Score[]>—an uncompleted task with the promise of having a
Score[] when finished. Any time a method returns a task, you can optionally “await” it with the await
keyword, which creates a situation similar to the ContinueWith that we saw in the previous section.
The preliminary code before the await happens immediately, by the thread that called the method.
Once an await is reached, the thread that called the method jumps out of the method and continues
merrily on its way to something else. The asynchronous code will either happen on a separate thread (if
you use Task.Run) or perhaps by no thread at all (if it’s waiting for the network or file system to complete
some work).
But everything after the await is scheduled to happen when the task finishes. The await effectively slices
the method in two while still using syntax that is easy to understand.
You’ll notice from the previous code sample that the await keyword also extracts the value out of the
Task<Score[]>. We don’t have to call Task.Result to get it. If a method returns a generic Task<TResult>,
then the await keyword will do that for you. In other words, it extracts the promised value out of the task
upon completion.
If a method returns just a Task, the non-generic version which doesn’t have a promise value, you can still
await it. It just has to be structured as a statement, rather than an assignment.
Console.WriteLine("Before await.");
await SomeMethodThatReturnsTask();
Console.WriteLine("After await.");
The await keyword can’t be used just anywhere. It can only be called inside a method that is marked with
the async keyword. You can see that I added that in the earlier code:
public async void GrabHighScores()
You can’t put the async keyword on just any method. There are some limitations. The official rule is that
you can put async on anything that has a void return type, or a valid “awaitable”. The rules that define
what a valid awaitable is are kind of complicated, so we’ll skip the details here. In short though, you’ll
nearly always use either the non-generic Task class, the generic Task<TResult>, or more rarely, the
generic ValueTask<TResult> struct. In the extremely unlikely event that none of these suit your purposes,
you can look up the rules for making a custom awaitable, and make your own.
When do you use each of the four standard options?


An async method with a void return type implies a fire-and-forget nature; you don’t care when it
finishes, just that it happens asynchronously.
An async method with a Task for the return type implies you care about when it finishes,
including scheduling stuff after the task completes (with await) but that the task itself doesn’t
return any meaningful data.
258
Chapter 40
Asynchronous Programming
An async method that returns the generic Task<TResult> implies the task returns a value (it
promises a value upon completion) and allows you to grab it when it’s done.
ValueTask<TResult> is used in similar instances as Task<TResult>, with the difference being
ValueTask<TResult> is a value type instead of a reference type like Task<TResult>. (ValueTask is
not accessible out of the box. You must add a reference to the System.Threading.Tasks.Extensions
NuGet package as described in Chapter 46.)


Asynchronous programming can be tricky. This chapter has hopefully demystified it to a large extent, but
it takes a lot of time and practice to get good at asynchronous programming (in any language). But at least
we’ve now gotten that particular adventure off on the right foot.
Try It Out!
Asynchronous Programming Quiz. Answer the following questions to check your understanding.
When you’re done, check your answers against the ones below. If you missed something, go back
and review the section that talks about it.
1.
2.
3.
4.
5.
6.
7.
Define asynchronous programming.
Indicate which of the following could benefit from asynchronous programming:
a. A computationally expensive process like copying all pixels from one image to
another.
b. A small math operation, like an addition or multiplication.
c. An operation that does little work of its own, besides waiting for a server to respond
across the Internet.
What keyword is used to indicate that a method may complete some of its work
asynchronously after returning?
What keyword is used to indicate that code further down the method should not be
executed until an asynchronous task has finished?
Identify at least 3 return types that can be used with the async keyword.
True/False. Code is always faster when ran asynchronously.
Matching. Match the asynchronous return type on the left with the correct option on the
right:
____ void
____ Task
____ Task<TResult>
A. You care when the task finishes, but it does not have a
specific result to return.
B. You don’t care when the asynchronous task finishes; fireand-forget.
C. The asynchronous task has a specific result that it
returns.
Answers: (1) Running code on a separate thread, usually to improve responsiveness or speed. (2) A: Yes. B: No. C: Yes. (3)
async. (4) await. (5) void, Task, Task<TResult>, ValueTask<TResult>, etc. (6) False. (7) B, A, C.
41
41 Dynamic Objects
In a Nutshell






Dynamic type checking happens at run-time, not compile time.
The dynamic type tells the compiler to do dynamic type checking for the variable.
Dynamic objects allow you to add and remove members at run-time.
IDynamicMetaObjectProvider tells the dynamic language runtime how to look up members
dynamically.
ExpandoObject allows for simple, growable dynamic objects.
Deriving from DynamicObject allows for greater control over dynamic members.
Types are a big deal in C#. We spend a lot of time defining what a new class or struct will look like. We
worry about getting inheritance hierarchies right. We carefully cast to and from types, and make sure that
everything is of the type it ought to be when assigning variables and calling methods.
Throughout the bulk of the C# world, types are considered “static” at run-time. (Not to be confused with
the static keyword.) That is, you define a class, add properties and methods, and work with the class in
the rest of your code. But the type can’t change while the program is executing. Methods can’t be added
while the program is running. New properties can’t be defined on the fly. This puts C# in the category of
statically typed languages. Or it could be said that the compiler does static type checking.
There are some advantages and disadvantages to this. The primary advantage is that the compiler can
guarantee that everything you do is “safe.” If you attempt to call a method on an object, the compiler
makes sure that the method exists. Reading a value from a property will work, because you know it will be
there. Any errors of this nature are caught when you try to compile your program.
On the other hand, having the flexibility to add new methods, properties, and other elements at run-time
is a powerful proposition. This is called dynamic typing, and a number of languages (Python, Ruby,
JavaScript, Lua, etc.) allow this on any object. To be clear, with dynamic typing, the system still makes sure
that the member is there when used; it just does so at run-time instead of at compile time. This is called
dynamic type checking.
260
Chapter 41
Dynamic Objects
Back in C# 4.0, C# added the ability specify that certain variables should be checked at run-time instead of
at compile time (dynamic type checking). It also added support for dynamic objects—objects where
properties, methods, and other members could be determined or modified while running. This chapter
covers how to utilize both dynamic type checking and dynamic objects in your C# code.
Dynamic Type Checking
With the familiar statically typed variables, you know its type, and can perform operations on it. The
compiler can verify that operations that you do with it are safe at compile time. For example, in the
following code, it can verify that the string type actually has a Length property, or that Console actually
has a WriteLine method.
string text = "Hello World!";
Console.WriteLine(text.Length);
On the other hand, C# also has a dynamic type. Any variable or object can be stored in this type.
dynamic text = "Hello World!";
Console.WriteLine(text.Length);
Making something dynamic tells the compiler that it should not do static type checking. That is, it will not
check to make sure that methods, properties, operators, etc. actually exist on the type at compile time,
but rather, wait until run-time to check it. For example, look at the various operations that we attempt to
do on the text variable in the following code:
dynamic text = "Hello World!";
text += Math.PI;
text *= 13;
Console.WriteLine(text.PurpleMonkeyDishwasher);
None of these are things that can be done with a string. Indeed, if we had left the type of text as string,
this wouldn’t compile. But when the variable is made dynamic, we are telling the compiler, “Don’t verify
that the methods we attempt to use on this actually exist at compile time. Wait until the program is
running and you reach that code.” Since text has a type of dynamic, this code compiles. But because
string doesn't have the ability to add a double, multiply by an int, or have a PurpleMonkeyDishwasher
property, any of these will fail at run-time with a RuntimeBinderException.
When there is a dynamic variable, the compiler won’t fail just because it can’t find a needed member of
the type. Instead, it records some information about what is being requested in that particular spot
(particularly the name of the member being used) so that at runtime, it can dig through that information
and use it to figure out if the member exists or not.
While the code above shows that any object can be assigned to a dynamic variable (which we did with a
string) doing so just isn’t very practical unless the object is actually dynamic itself—that is, in some form
or fashion, the type can either change at run-time, or you don’t know what members it will have at
compile time.
Dynamic Objects and the Dynamic Language Runtime
C# allows you to define objects that are modifiable or constructed while the program is actually running.
Where this is the case, these objects are considered dynamic objects.
Dynamic objects were introduced in C# 4.0, when the Dynamic Language Runtime was added to the .NET
platform. The Dynamic Language Runtime, or DLR for short, is a component that provides all of the
tooling and infrastructure needed to enable dynamic typing and dynamic objects to the .NET platform.
Emulating Dynamic Objects with Dictionaries
261
The primary goal here was to allow dynamic languages such as Ruby (IronRuby) and Python (IronPython)
to be able to run on the .NET platform itself. A side product of that was the introduction of the dynamic
type and dynamic objects to C# as well.
Any type can be a dynamic object by implementing the IDynamicMetaObjectProvider interface. By
implementing this interface, you can tell the DLR how it should look up dynamic properties, methods, and
other members for a given type.
Unfortunately, IDynamicMetaObjectProvider is extremely low level, requiring you to do
“metaprogramming,” which requires your code to analyze other code and reason about it, and does so at
the expression and statement level. This is both tedious and error prone.
Fortunately, you will almost never need to implement IDynamicMetaObjectProvider unless you’re doing
something crazy like adding another new dynamic language to the .NET Platform. Instead, there are two
much simpler and more fun options: ExpandoObject and DynamicObject. We’ll talk about both of these
in some depth to illustrate how to use these, and skip the details of IDynamicMetaObjectProvider
(which deserves its own book).
Emulating Dynamic Objects with Dictionaries
The first thing we should talk about is how we might get dynamic-like behavior without using actual
dynamic objects. The reason for this is that is serves as a good frame of reference. All dynamic objects are
actually implemented in a similar way to what we’ll talk about here, just with better syntax.
If you wanted to emulate a dynamic object, where properties and methods can be added or removed on
the fly, one option is to use the Dictionary class (Chapter 25). Specifically, a Dictionary<string, object>
would allow us to add new data by name (much like a property name, just as a string instead).
For example:
Dictionary<string, object> poorMansDynamicObject = new Dictionary<string, object>();
poorMansDynamicObject["Name"] = "George";
poorMansDynamicObject["Age"] = 21;
By comparison, you could imagine having a normal class to represent the above with a Name and Age
property. But on the other hand, using a dictionary like this allows us to add in any new “property” that we
want on the fly. It doesn’t have to be determined before compiling. That gives us a lot of flexibility.
You could also add something that resembles a method in a similar way using delegates:
poorMansDynamicObject["HaveABirthday"] = new Action(() =>
poorMansDynamicObject["Age"] = (int)poorMansDynamicObject["Age"] + 1);
(Note that we're using the Action type here, which we talked about in Chapter 32.) While the syntax is a
bit awkward, you can invoke delegates directly. We could call this method like so:
((Action)poorMansDynamicObject["HaveABirthday"])();
In that code, we pull the Action object out of the dictionary, cast it to an Action, and then invoke it (the
parentheses at the end).
Interestingly, with a dictionary, we could even remove elements dynamically using the Remove method.
These examples show how we could make something with dynamic-like features using a dictionary,
though the syntax is quite awkward. We’ll fix that by using actual dynamic objects, but keep in mind that
behind the scenes is usually something similar to the dictionary approach we just saw.
262
Chapter 41
Dynamic Objects
ExpandoObject
The first option we have for true dynamic objects is a class called ExpandoObject. ExpandoObject is
effectively just the Dictionary<string, object> that we just saw, but it also implements
IDynamicMetaObjectProvider which means we get much better syntax for working with it.
Below is code that uses ExpandoObject to do the same stuff as the previous section with a dictionary:
dynamic expando = new ExpandoObject(); // Requires a 'using System.Dynamic;' directive in your file.
expando.Name = "George";
expando.Age = 21;
expando.HaveABirthday = new Action(() => expando.Age++);
expando.HaveABirthday();
You can that the syntax here is drastically improved. Adding properties is as simple as just assigning a
value to them. Our method for incrementing age became far cleaner. And even our method call at the
end is very readable, and looks like a normal method call.
This is all thanks to the magic of dynamic typing. ExpandoObject implements
IDynamicMetaObjectProvider, and because of that, it defines how the dynamic type system should look
up members like properties and methods at run-time, producing very clean syntax as shown above.
Interestingly, ExpandoObject actually implements IDictionary<string, object>, though it does so
“implicitly.” But that means if you cast an ExpandoObject to IDictionary<string, object> you can do
some additional cool things like enumerate all members that an ExpandoObject currently has or even
delete a member:
IDictionary<string, object> expandoAsDictionary = (IDictionary<string, object>)expando;
foreach(string memberName in expandoAsDictionary.Keys)
Console.WriteLine(memberName);
expandoAsDictionary.Remove("Age"); // Remove the `Age` property.
After this code runs, if you attempt to access Age again, it will fail with a RuntimeBinderException.
This begins to illustrate the power of dynamic objects in general, and ExpandoObject specifically. Having
an object where you can add and remove members like properties and methods is extremely powerful.
Extending DynamicObject
A second option for dynamic objects is to derive from the DynamicObject class. Contrasted with
ExpandoObject, which is fairly light-weight, DynamicObject is a bit trickier, but gives you more control
over the details. DynamicObject is a more powerful tool.
DynamicObject is an abstract class with a pile of virtual methods that can be overridden. While you can’t
create an instance of DynamicObject itself, you can derive a new class from DynamicObject and then
override the methods it contains to add the functionality you want it to have.
Each of these methods allow you to do customize the behavior of one aspect of your dynamic type. For
example, how it should retrieve property values, how it should set property values, how it should invoke
methods, etc. The way DynamicObject is structured, you only have to override the methods that you
care to have function.
The example below illustrates how you might override DynamicObject to support a supplied set of
dynamic string typed properties:
Extending DynamicObject
public class CustomPropertyObject : DynamicObject
{
private Dictionary<string, string> data;
263
// Requires a 'using System.Dynamic;' directive.
public CustomPropertyObject(string[] names, string[] values)
{
data = new Dictionary<string, string>();
for (int index = 0; index < names.Length; index++)
data[names [index]] = values[index];
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (data.ContainsKey(binder.Name))
{
result = data[binder.Name];
return true;
}
else
{
result = null;
return false;
}
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
if (!data.ContainsKey(binder.Name))
return false;
data[binder.Name] = value.ToString(); // ToString(), in case it isn't already a string.
return true;
}
}
You can see that this type derives from DynamicObject, and overrides the TryGetMember and
TrySetMember properties. These two pieces are the crux of making a DynamicObject-based
implementation.
You can also see that—like with many things of a dynamic nature—a dictionary was used to store the
existing properties and their values.
The constructor is relatively straightforward. It takes the two input arrays (the names of the properties
and their values) and constructs a dictionary out of the pairings.
The overrides for TryGetMember and TrySetMember define how the dynamic runtime should handle
property getters and setters. You can see a bit of a pattern there. Each has a binder parameter that
defines what member is being looked at (binder.Name). In the getter, we use the name to look up the
right value from the dictionary. In the setter, we update the dictionary with the new value.
In both cases, if the property being requested doesn’t exist, we return false. This tells the dynamic
runtime that the member being requested doesn’t actually exist. At least with the setter, we could have
added the new property and set it (dynamically adding the property, like what happens with
ExpandoObject.) But in this particular sample, I chose to not do that. When false is returned, the DLR can
do a number of things with a failed member access. In the C# world, this generally is going to result in a
RuntimeBinderException.
There are many other methods that are a part of DynamicObject that can be overridden, depending on
what you’re trying to accomplish:
264
Chapter 41








Dynamic Objects
GetDynamicMemberNames: Allows the dynamic object to list all dynamic members it contains.
TryUnaryOperation and TryBinaryOperation: Allow you to overload operators.
TryConvert: Allows you to include user-defined conversions.
TryGetIndex and TrySetIndex: Let you provide an indexer for your type. There is also a
TryDeleteIndex, but that doesn’t work in the C# world, only other languages (like Python).
TryInvokeMember: Allows you to provide dynamic methods.
TryInvoke: Allows you to treat the object itself as a method, as though it were a delegate.
TryCreateInstance: Allows you to create dynamic constructors, but this can’t be done in C#.
TryDeleteMember: Could be used to delete members, though no C# syntax for it.
Try It Out!
Dynamic CSV Importer. Review the section in Chapter 29 that describes the CSV file format, and how
it was used there to store high scores.
Create a project that will use code similar to the previous DynamicObject code sample to read in
data from a CSV file and produce dynamic objects with properties that match the headers in the file.
Then write out the data to the screen in a format like, “[Player] had a score of [Score].”
When to Use Dynamic Object Variations
In this final section, we’ll discuss when you should prefer each of the variations of dynamic objects.
To start, I should point out that dynamic objects should only be used when necessary. Static type checking
is extremely useful, and you should only revert to dynamic type checking when you absolutely need either
objects whose members can’t be determined until run-time, or whose members can change at run-time.
If you need this, then using dynamic objects is fine. Otherwise, stick to normal classes and structs.
You should also consider whether just using a dictionary to store your data would be a simpler option.
Using dynamic objects requires a different thought pattern than the “normal” statically typed objects that
C# programmers are accustomed to. In some situations, just using a dictionary will be easier than going
with full-blown dynamic objects.
If you do need actual dynamic objects, then your options are using ExpandoObject, deriving from
DynamicObject, and reimplementing IDynamicMetaObject.
ExpandoObject is perfect for simple scenarios where you need to be able to add properties (and less
frequently methods) on the fly.
Deriving from DynamicObject is better for situations where there are rules that govern what properties,
methods, and other members should exist for the type. The fact that you can program the rules into the
object make it so you can’t add things that shouldn’t be there, which isn’t the case with ExpandoObject.
Additionally, DynamicObject allows you to customize virtually every aspect of your type, including
operators and indexers, which ExpandoObject doesn’t support.
You should generally avoid implementing IDynamicMetaObject from scratch. It is very difficult to do,
hard to debug, and very error-prone. Unless you’ve become a dynamic object guru and need to optimize
the tiny nuances of the dynamic language runtime, or are trying to add a new dynamic language to the
.NET Platform, steer clear of implementing IDynamicMetaObject. Stick with ExpandoObject or
DynamicObject instead.
42
42 Unsafe Code
In a Nutshell








“Unsafe code” allows you to directly reference and manipulate memory locations.
Unsafe code is primarily used for interoperating with native code.
Avoid unsafe code when possible; most applications won’t have any need for it.
Unsafe code can only be used in unsafe contexts, determined by the unsafe keyword.
Pointers allow you to reference a specific memory location.
fixed can be used to “pin” managed references in place so a pointer can reference them.
The stackalloc keyword allows you to define arrays whose data is stored on the stack.
You can invoke native/unmanaged code using Platform Invocation Services (P/Invoke).
Before you read this chapter, I should warn you that you probably don’t need this chapter at all, and
should probably skip it. At least until it becomes clear that you do.
One of the key features of the C# language and the .NET Platform is that it manages memory for you. You
don’t have to manually allocate or free memory. Earlier languages like C and C++ didn’t have this feature.
This is an extremely desirable feature, and one of the biggest selling points of C#. But sometimes, you
find the need to interoperate with code that doesn’t have this feature—code that does direct memory
allocation and manipulation. Working with C and C++ code is a prime example of this. In these cases, you
may find that you have to leave the managed memory world and enter the unmanaged, “unsafe” world.
If you aren’t doing this in your projects, then I strongly advise skipping (or just skimming) this chapter.
Working with unsafe or unmanaged code can be tricky. There are many ways that you can shoot yourself
in the foot. Going through the ins and outs of unsafe code deserves an entire book, and I can’t do it justice
here. But this gives you a starting point in the event that you find yourself diving into unsafe code.
Unsafe Contexts
Most C# code does not need to jump out of the realm of managed code and managed memory. However,
C# does support certain “unsafe operations”—data types, operators, and other actions that allow you to
directly reference, modify, and allocate memory when needed.
266
Chapter 42
Unsafe Code
These unsafe operations can only be done in an unsafe context. This is done to ensure that programmers
don’t inadvertently use these unsafe operations unintentionally.
The name “unsafe” is somewhat misleading. Unsafe code doesn’t mean the code is truly dangerous, just
that the code is unverifiable—the compiler can’t guarantee safety.
It is easy to make a chunk of code an unsafe context by wrapping it in an unsafe block like so:
public void DoSomethingUnsafe()
{
unsafe
{
// You can now do unsafe stuff here.
}
}
Inside of the unsafe block, you will be able to use pointer types (which we’ll discuss soon) and other direct
memory manipulation tasks. In effect, an unsafe context is a little like being able to write a small chunk of
C or C++ code directly within your C# application.
You can make a whole method an unsafe context by adding the unsafe keyword to the method signature:
public unsafe void DoSomethingUnsafe()
{
}
A type such as a struct, class, or interface can be marked with the unsafe keyword as well, which makes it
so every method inside is an unsafe context:
public unsafe class C
{
}
But creating an unsafe context is not quite enough. You also have to tell the compiler that you want to
allow unsafe code in your project. This can be done by using the /unsafe compiler option on the
command line, or by configuring your project to allow for unsafe code within Visual Studio itself.
To set up a project to allow unsafe code, follow these steps:
1.
2.
3.
Right-click on the project in the Solution Explorer and choose Properties.
Select the Build tab on the left.
Check the box that says, “Allow unsafe code”.
Without this, you will get the error “CS0227: Unsafe code may only appear if compiling with /unsafe.”
Pointer Types
In an unsafe context, you can create variables that are pointer types. A pointer type is a variable that
identifies a specific memory address where another object lives. This is conceptually similar to the
reference types that we’ve been working with throughout this book, but has some huge differences on a
practical level. While both pointers and references both direct you to some other object, a pointer
contains a raw memory address, while a reference is indirect, and maintained by the garbage collector.
The garbage collector is the part of the CLR that is responsible for managing objects that are alive in RAM.
This includes rearranging the data for efficiency and freeing up discarded objects to free up the memory.
As the garbage collector moves data around, it also maintains references. But the garbage collector does
not control pointers, so those are not updated.
You declare a pointer type with a * by the type:
Stack Allocations
267
int* p; // A pointer to an integer.
You can create a pointer to any of the numeric types, bool, enumerations, any pointer types (pointers to
pointers) and any structs that you’ve made, as long as they don’t contain references.
C# has borrowed three operators from C++ for working with pointer types: The address-of operator (&)
for getting the address of a variable, the indirection operator (*) for dereferencing a pointer to access the
object it points to, and the pointer member access operator, for accessing members such as properties,
methods, etc. on a pointer type object. These are shown below:
int x;
unsafe
{
// Address-Of Operator. Gets the memory address of something else and returns it.
// This puts the address of `x` and puts it in pointerToX.
int* pointerToX = &x;
// Indirection Operator: Dereferences the pointer, giving you the object at the location pointed to
// by a pointer. This puts a 3 in the memory location pointerToX points at (the original x variable).
*pointerToX = 3;
// Pointer Member Access Operator: Allows you to access members through a pointer.
pointerToX->GetType();
}
Pointer types are the third and final major category of data types available in C#, next to value and
reference types, completing our type hierarchy chart that we introduced in Chapter 6.
Stack Allocations
Because normal C# arrays are reference types, the array data is placed in the heap somewhere. An array
variable stores a reference to it, rather than the data itself (see Chapter 16). Look at the following code:
public void DoSomething()
{
268
Chapter 42
Unsafe Code
int[] numbers = new int[10];
}
When this method is first called, a new frame is placed on the stack with enough memory allocated to
store local variables—numbers in this case. This will be a reference to an array, so 4 bytes are allocated
for this. When the new int[10]; part is executed, the memory for the array contents is created in the heap
(10 * 4 bytes for an int = 40 total bytes). When this is assigned to the numbers variable, the reference for
this new memory is placed in the numbers variable itself.
When the method returns and the frame for it on the stack is removed, the 4 bytes for numbers is
immediately cleaned up, but the 40 bytes in the heap remains for a while. If there are no other references
to it in the system, the garbage collector will clean it up eventually.
This behavior is usually not just tolerable, but desirable. But sometimes, in order to interoperate with
native code or if you just have to get that memory cleaned up the instant we return from the method, C#
allows us to force the array contents to be placed in the stack itself with the stackalloc keyword:
public unsafe void DoSomething()
{
int* numbers = stackalloc int[10];
}
Now when you call this method, the 40 bytes for the array are allocated on the stack, and not in the heap.
The garbage collector doesn’t need to clean this up. When the method terminates, the method’s stack
frame is removed, along with all memory for it, which now includes the 40 bytes of data. The garbage
collector doesn’t have to deal with it, and it is immediately cleaned up.
stackalloc can only be used for local variables, and only in an unsafe context.
Fixed Statements
When working with unsafe code, one of the things you have to deal with is that in a managed
environment, the garbage collector can move data around in memory to optimize it. When you’re using a
managed reference, those movements are abstracted away from you. It is OK if something moves on you
because the reference is maintained, and will point to the new location as expected.
On the other hand, when we use pointers, we are using a raw memory address that aren’t managed. If we
are trying to work with a managed object in our unmanaged, unsafe code, it is possible that the object
we're pointing at gets moved out from under us without us knowing. This would cause huge problems.
Fortunately, there’s a way to tell the garbage collector to (temporarily) not move a specific object. Suppose
we have a Point class defined with public instance variables like this:
public class Point
{
public double X;
public double Y;
}
If we want to get a pointer to an object's X field, we would want to make sure that the garbage collector
doesn’t try to move the Point object itself until we’re done using it. To do this, you can use a fixed
statement to “pin” a managed object in place as you’re getting its address, like the following:
Point p = new Point();
fixed(double* x = &p.X)
{
Fixed Size Arrays
269
(*x)++;
}
Doing this will pin p in place for the duration of the fixed block.
A few minor points to mention with fixed statements:





You must declare the pointer variable in the fixed statement. You can’t use a pre-existing one.
You can declare multiple pointer variables of the same type in a single fixed statement by
separating them by commas.
You can nest multiple fixed statements inside each other.
Pointer variables created in a fixed block can’t be modified to point to something else.
After the fixed block, the pointer variable (x in the above code) is out of scope and can’t be used.
Fixed Size Arrays
In C#, an array variable can reference arrays of different lengths during its lifetime:
int[] numbers = new int[10];
numbers = new int[1000];
In some languages like C and C++, this isn’t possible; the array length is baked into the variable’s type. If
you are interoperating with code that expects fixed size arrays, the mismatch is a problem.
Fortunately, C# does allow you to declare arrays that are fixed size, to facilitate working with code that
requires it. You can only declare them in a struct, but this limitation is not very limiting, as this is the exact
scenario where you would want them anyway.
To shed a little more light on why you would even want fixed size arrays, consider the following struct:
public struct S
{
public int Value1;
public int Value2;
public int[] MoreValues;
}
How many bytes will this struct be? If we try to pass this to native, unmanaged code, what would be sent?
This struct would be 12 bytes in size: 4 bytes for Value1, 4 bytes for Value2, and 4 bytes for the reference
that is contained in MoreValues. The array data is not contained in a struct instance. A reference to the
data is instead. (It is a reference type, after all.)
When you call native code that stores array data in place, normal C# arrays won’t be compatible.
But C# allows you to declare fixed size arrays (sometimes called fixed size buffers) to facilitate working with
native code that requires it. The following code sample illustrates making a fixed size array:
public unsafe struct S
{
public int Value1;
public int Value2;
public fixed int MoreValues[10];
}
To make an array a fixed size array, you put the fixed keyword before the type. The size of the array goes
at the end of the variable name inside of square brackets, and note that the type is int instead of int[].
270
Chapter 42
Unsafe Code
With this declaration, the struct itself will contain the data for the array, rather than a reference to data
that lives elsewhere. This means that the size of this version of S will now be 48 bytes instead of 12: 4 for
Value1, 4 for Value2, and then 4 * 10 = 40 for MoreValues.
A struct must be marked with the unsafe keyword in order to use fixed size arrays in it.
It is important to point out that the runtime does not perform any sort of index bounds checking on
these. If you created an instance of S, you could access MoreValues[33] and access some other arbitrary
memory location beyond the array, which is a dangerous toy to be playing with. (Which is why it can only
appear in an unsafe context).
Calling Native Code with Platform Invocation Services
Platform Invocation Services, or P/Invoke for short, allows your managed C# code to call native code
directly. This is powerful because there is a lot of native code out there that you can utilize, including
native C or C++ libraries, operating system calls, or your own C++ code.
The managed C# world and the unmanaged world are quite different from each other. Conversions
between managed references and unmanaged pointers and the nuances of marshalling the data across
this boundary, means P/Invoke can get complicated.
A simple example is in order here. Let’s assume that you have some C++ code that defines an add
method to add two integers together. In your C++ code, you would make sure this method is exported
from your DLL (a C++ topic for a different book). In your C# code, you would then be able to import this
add method using the DLLImport attribute and the extern keyword:
public static class DllWrapper
{
[DllImport("MyDLL.dll", EntryPoint="add")]
static extern int Add(int a, int b);
}
When you use the extern keyword, you do not provide a body for the method. That is because the actual
code for it is defined externally, in your native code library.
Additionally, all extern methods must also be static.
The DllImport attribute tells the compiler that it needs to transform calls to this method into a P/Invoke
call. This attribute specifies the information needed to perform the correct P/Invoke call. At a minimum,
the name of the DLL (MyDLL.dll) must be included. The EntryPoint (function name) must also be
included if the two don’t share the exact same name. DLLImport has some additional properties not
shown here, which can be used to manage the nuances of calling the native code.
With this setup, calls to DllWrapper.Add(3, 4) will now jump over to the unmanaged DLL library and
invoke the native add method, and return with the computed result.
This is a trivial example of calling native code, and real examples can get much more complicated. In
particular, getting the signatures right and configuring the DllImport attribute properties correctly can be
a huge headache. The website http://www.pinvoke.net can be very helpful in getting this just right.
43
43 Other Features in C#
In a Nutshell

















This chapter covers a large collection of random advanced topics. The purpose of this chapter
is to ensure that you know they exist.
yield return produces an enumerator without creating a container like a List.
const and readonly define compile-time and runtime constants that can’t be changed.
Attributes let you apply metadata to types and their members.
The nameof operator gets a string representation of a type or member.
The sizeof operator returns the size of a type.
The bit shift operators let you play around with individual bits in your data.
Reflection allows you to inspect code while your program is running.
IDisposable lets you run custom logic on an object before being garbage collected.
C# defines a variety of preprocessor directives to give instructions to the compiler.
Nullable types allow value types to take on a value of null.
The ?. and ?[ ] operators let you perform succinct null checks: a?.Property?.Method();
You can read in command line arguments from the command prompt.
You can make your own user-defined conversions between types.
The (dangerous) goto keyword allows you to instantly jump to another location in a method.
Generic variance governs how a type parameter’s inheritance affect the generic type itself.
Checked contexts will throw exceptions when they overflow. Unchecked contexts do not.
In this chapter, we’re going to cruise through a variety of features and tricks that we haven’t talked about
yet. Some of these are very useful but aren’t big enough for their own chapter. Others are less useful, so
we’ll just cover their basics, and let you to explore it in depth if you feel the need.
There is a lot in this chapter and I don’t expect you to become a master of it all overnight. Some of these
topics are so big that it you could write books about it. Many of these topics may not ever apply to you.
There are two real purposes to this chapter. One is to open your eyes to the possibilities with C#. The
other is to make it so that when you see these things, instead of being completely blindsided by it, you’ll
at least be able to say, “Hey, I remember reading something about this!”
272
Chapter 43
Other Features in C#
You don’t necessarily need the stuff in this chapter to write C# programs. Everything we’ve covered up
until now will go a very long way. This chapter will help tie together some of the loose ends.
Iterators and the Yield Keyword
Way back in Chapter 13, we first introduced foreach loops. In Chapter 25 we introduced the
IEnumerable<T> interface, and stated that anything that implements IEnumerable<T> can be used in a
foreach loop. Remember, an IEnumerable is simply anything where you can examine multiple values in
a container or collection, one at a time. This capability makes types that implement this interface iterators.
In addition to a direct IEnumerable implementation, you can additionally define an iterator using the
yield keyword. This would look something like this:
class IteratorExample : IEnumerable<int>
{
public IEnumerator<int> GetEnumerator()
{
for (int index = 0; index < 10; index++)
yield return index;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Much of this code should make sense by now. We’re simply implementing the IEnumerable<int>
interface, which requires us to have the GetEnumerator method that you see. And since the
IEnumerable<T> interface is derived from the non-generic IEnumerable interface, we also need to
define that second method, which just simply calls the first method.
The part that will likely seem strange to you is the yield return. As we’re iterating, the method will be
called multiple times, and each time, it will return the next item that is “yielded.” Unlike a normal return
statement, when we use yield return, we’re saying, “pause what you’re doing here and return this value,
but when you come back, start up again here.” That is very powerful. For example:
public IEnumerator<int> GetEnumerator()
{
yield return 0;
yield return 1;
yield return 4;
yield return 6;
}
Interestingly, the compiler isn’t simply turning all yielded values into a list. It is happening one at a time.
This means you could theoretically create an iterator that never ends:
public IEnumerator<int> GetEnumerator()
{
int number = 0;
while(true)
{
yield return number;
number++;
}
}
The calling code could keep calling the iterator forever, or stop when it has completed its job.
Constants
273
The yield return syntax cannot be used anywhere, just inside of methods that return IEnumerator<T>
or IEnumerable<T> and their non-generic counterparts.
Named Iterators
There’s a second way to do iterators. Instead of implementing the IEnumerable<T> interface and adding
a GetEnumerator method, you can create a method that returns an IEnumerable<T>. You can then use
this method in a foreach loop whenever you want. This approach lets you have multiple iterators for a
single data collection class, and it lets you have parameters for those methods:
class Counter
{
public IEnumerable<int> CountUp(int start, int end)
{
int current = start;
while (current < end)
{
yield return current;
current++;
}
}
public IEnumerable<int> CountDown(int start, int end)
{
int current = start;
while (current > end)
{
yield return current;
current--;
}
}
}
You then call these iterators like this:
Counter counter = new Counter();
foreach (int number in counter.CountUp(5, 50))
Console.WriteLine(number);
foreach (int number in counter.CountDown(100, 90))
Console.WriteLine(number);
Constants
We’ve spent a lot of time talking about variables. True to their name, the contents of a variable can...
well... vary. But there are times where we want to assign a value to something and prevent it from ever
changing again. C# provides two ways of addressing this: the const and readonly keywords.
If you mark a variable with either of these, then you will not be allowed to change the value of the variable
later on. The two are subtly different. Let’s start with the const keyword. In this book, we’ve used Math.PI
several times. If we made our own Math.PI variable, we could use the const keyword to define PI like this:
public const double PI = 3.1415926;
Adding in const tells the compiler that we’re assigning it a value and it will never change. In fact, if we try
to assign a value to it at some point in our program, we’ll get a compiler error. If you have a value that you
know will never change, it is a good idea to make it into a constant by adding the const keyword to it. That
way, no one will mess it up on accident.
274
Chapter 43
Other Features in C#
Anything marked with const is automatically treated as though it were static. Because this is assumed
(and required) you don’t need to (and can’t) make it static yourself.
The readonly keyword is similar to this, but has an important difference. Anything that is marked const
must be assigned a value right up front, when the program is being compiled. As such, they are often
called compile-time constants. Things that are marked with readonly can still be assigned a value in a
constructor or at the same place it is declared, and not after. These are called runtime constants.
A runtime constant isn’t known at compile time, but once it has been assigned a value, it can’t be
modified. For example, we could create a Point class that stores an x- and y-coordinate in such a way that
once it has been created, it is impossible to change the values.
public class Point
{
private readonly double x;
private readonly double y;
public double X { get { return x; } } // We could have also just used a readonly property
public double Y { get { return y; } } // for these, but this illustrates the point well.
public Point(double x, double y)
{
this.x = x;
this.y = y;
}
}
We can then use this class, creating two point with different values, but both are unchangeable:
Point p1 = new Point(5, -2);
Point p2 = new Point(-3, 7);
This, by the way, is an excellent way of building immutable types like we discussed in Chapter 21.
Attributes
Attributes allow you to add metadata to an element of your code. They can be added to types that you
create or their members. They can even be applied to a method’s parameters and return types.
These attributes can be detected by the compiler, external code tools like unit testing frameworks, or
even detected while your code is running, using reflection. (We’ll talk about reflection in a second.)
There are hundreds of attribute types that come with the .NET Platform. We can’t cover them all. When
you learn a tool that uses attributes, they’ll walk you through the attributes that you’ll need to know.
But to illustrate the basics of how attributes are used, we’ll look at one specific but simple attribute called
the Obsolete attribute. You can use this to mark classes and methods that are out of date, and should no
longer be used. To apply an attribute to a method, you put the attribute name in square brackets ([ and ])
above the element:
[Obsolete]
private void OldDeadMethod()
{
}
Depending on what namespace the attribute lives in, they may require an additional using directive.
The compiler uses this particular attribute to detect calls to obsolete methods and warn the programmer
about it. If you use a method with this attribute on it, you’ll see a warning in the Error List telling you so.
Many attributes have certain parameters that you can set as well, including the Obsolete attribute:
The ‘nameof’ Operator
[Obsolete("Use NewDeadMethod instead.", true)]
private void OldDeadMethod()
{
}
In this case, the first parameter is text that will be displayed in the warning message, and the second
indicates whether the compiler should treat the problem as an error rather than a warning.
Attributes are simply classes that are derived from the Attribute class, and you can make your own.
When we use an attribute, we’re essentially calling a constructor for that class.
Multiple attributes can be applied to an element:
[Attribute1]
[Attribute2]
[Attribute3]
public class MagicClass
{
}
When creating an attribute, people often put Attribute at the end of the name for their attribute (e.g.
ObsoleteAttribute). In these cases, you can use either [Obsolete] or the longer [ObsoleteAttribute].
For specific instructions on how to create your own attributes, see: http://msdn.microsoft.com/enus/library/84c42s56.aspx.
The ‘nameof’ Operator
It’s quite common to want to do something with the name of a property, method, type, or other bit of
code. Among other things, this is useful in debugging and when you’re doing data binding in a UI
framework like WPF or Windows Forms, where property names are frequently used.
Let’s consider a really simple example related to debugging, where we want to print out an object’s
properties and their values. Let’s say we’ve got a Book class like the following:
class Book
{
public string Title { get; set; }
private string Author { get; set; }
private int Pages { get; set; }
private int WordCount { get; set; }
public Book(string title)
{
this.Title = title;
}
}
Let’s say we want to override the ToString method to display the values of each of these properties:
public override string ToString()
{
return $"[Book Title={Title} Author={Author} Pages={Pages} WordCount={WordCount}]";
}
This lets us see the details of a book either by writing it to the console window or in Visual Studio’s
debugger, revealing the values of all of the properties. It will appear like this:
[Book Title=There and Back Again Author=Bilbo Baggins Pages=118 WordCount=54386]
275
276
Chapter 43
Other Features in C#
That’s all fine and good, but what happens when we decide we want to change the name of something?
For example, let’s say we decide to rename WordCount to Words to mirror the Pages property. It’s easy
to refactor a property name like this (Ctrl + R, Ctrl + R), but in this case, our text remains unchanged. We’ll
still print out the same text, which is now misnamed.
This is where the nameof operator comes in. The nameof operator allows you to refer to a variable, type,
or member, and the compiler will turn it into a string that matches the name. For example, consider the
following uses of the nameof operator:
Book book = new Book("There and Back Again") { Author = "Bilbo Baggins", Pages = 118, WordCount = 54386 };
Console.WriteLine(nameof(Book));
Console.WriteLine(nameof(book));
Console.WriteLine(nameof(Book.Pages));
This results in the following output:
Book
book
Pages
Look carefully at how that works. Microsoft has gone out of its way to make sure it works in an intuitive
way. For a type, it gives you the name of the type without the namespace. For the name of a variable, it
gives you the name of the variable. When you access a property like Book.Pages, you get the name of the
property. That syntax is special because you normally can’t access a class’s properties like that unless
they’re static properties (which this isn’t).
If we update our ToString override from earlier in this section to use the nameof operator, we can make
it much more resilient to changes in our code:
public override string ToString()
{
return $"[{nameof(Book)} {nameof(Title)}={Title} {nameof(Author)}={Author} " +
$"{nameof(Pages)}={Pages} {nameof(WordCount)}={WordCount}]";
}
Now any changes to variable, property, or class names will be reflected correctly in this code.
The ‘sizeof’ Operator
The sizeof operator is quite a bit like the nameof operator. To nobody’s great surprise, it returns the size
in bytes of a specific type.
For example, if you do sizeof(int), it will return 4, because the int type is four bytes big. This is actually a
quick and convenient way to determine the size of any of the built-in types if you forget and don’t have a
handy reference (like the one in the tables in the back of this book).
This tends to be most useful when you are doing byte manipulation in your code, which doesn’t apply to
all applications you could make. But it does have its uses.
For example, the following code will create a byte array big enough to hold six integers:
byte[] byteArray = new byte[sizeof(int) * 4];
The sizeof operator is considered a constant value for most of the built-in types, with exceptions for
decimal and string. (That is, byte, short, int, long, sbyte, ushort, uint, ulong, float, double, char and
bool all have a known, pre-defined value and those values are swapped out at compile time.)
Any other type theoretically can be used with the sizeof operator, but the size is more complicated. For
one, it is not a constant. That means it can’t be placed in a const. It must be determined at runtime. And
Bit Fields
277
because of some optimizations done by the host machine at the last second, it may not always return the
same value on every computer you run it on. It also must be done in an unsafe context, as we talked
about earlier in Chapter 42.
The above limitations do make sizeof somewhat less useful, but if a calculation actually does require
using the size of some datatype, using something like sizeof(long) is better than a hardcoded 8 because it
conveys why it is that value.
Bit Fields
We usually work with variables at a relatively high level, thinking of them as containers for numbers,
strings, or complicated classes. Behind the scenes, though, they are essentially little containers to store a
pile of bits. Every type of data that we’ve talked about is represented as bits.
To illustrate, let’s briefly look at how a byte stores its values. Remember, a byte can hold the values 0 to
255 in a single byte (8 bits). These values are stored using binary counting. So the number 0 is stored with
the bits 00000000, the number 1 is stored with the bits 00000001, the number 2 is stored with the bits
00000010, and so on.
There’s a lot to know about counting in binary, and if you haven’t had any exposure to it before, it is
probably worth taking some time to learn how it works.
There are a few cases where we actually want to sort of fall back down to that low of a level, and work with
individual bits and bytes. In fact, you may use this extensively in certain types of projects.
One of the popular ways of using the raw bits of data is a bit field. Let’s start our discussion with a brief
explanation of why people are even interested in bit fields. Let’s say you’re creating an online discussion
board or a forum. You’ll have lots of users, each with different permissions and abilities. Some people
may be able to delete posts, edit posts, add comments, etc., while others may not.
Knowing what we’ve discussed before, we can easily imagine storing each of these values as a bool. And
that gets the job done. But each bool variable takes up 1 full byte, and if you have dozens of settings per
user, and millions of users, that adds up very quickly.
One popular solution to this is to have multiple Boolean values be packed into a single byte, or a single
int. In one single byte, you can store eight Boolean values, with one in each bit. For each bit, a 1
represents a true value, and a 0 represents a false value. Something like this:
0
0
1
0
0
0
1
1
(Not used)
Account
Suspended
Create
threads
Delete
others’
posts
Edit
others’
posts
Vote on
posts
Edit own
posts
Create
posts
Doing this kind of trick is actually very widespread, especially if you’re talking about things like very large
data sets, operating systems, and sending things across the network.
Even though we’re treating our byte as a pile of Boolean values here, C# is going to want to present it to
you as the byte type, with a single value for the entire collection. The bit field above will be shown to you
as the number 35. Of course, since we don’t care about it as a complete number, but as a collection of
Boolean values or Boolean flags, we’ll need some tools to help us work with the raw bits in a bit field.
Fortunately, C# has those tools built in for us.
278
Chapter 43
Other Features in C#
Bit Shift Operators
There are two operators that will take the bits in a byte or other integral type, and move them over a
certain number of slots. These two operators are called bit shift operators. The left bit shift operator is
two less than signs together (<<) while the right bit shift operator is two greater than signs together (>>).
These operators look like little arrows that tell you which way the bits will be shifted.
To use the bit shift operators, you would do something like this:
int result = 0b00001101 >> 2; // Binary literal 13
In the above case, all of the bits get moved to the right two spots, giving us a result of 0b00000011
(decimal value of 3). Notice that as things are shifted, some bits drop off one end, which are just gone,
and 0’s are added to fill in empty spots on the other end.
The left bit shift operator works the same way:
int result = 0b00001101 << 2; // Turns into 0b00110100 or 52.
Bitwise Logical Operators
Think back to when we first discussed the logical && and || operators. Remember that && evaluates to
true if and only if both sides are true, and || evaluates to true if either side is true. There are a couple of
related operators that work on bits themselves, and do a similar thing. As we look at this, think of 1 as
being true, and 0 as being false.
The bitwise and operator is a single ampersand (&) and the bitwise or operator is a single vertical bar (|).
These operators go down the bits, one at a time, and do an and or or operation on each bit. This will
probably make more sense with an example. Imagine the two binary numbers:
01010101
11111111
If you use the bitwise and operator, the program will look at the first bit in each number. The top number
has a 0, while the bottom one has a 1. That’s like having a false and a true, which if they are combined
together with and result in false, or a 0.
01010101
11111111
0
Then you look at the next value in each number. They are both 1, which is like having a true and a true,
which when combined with and, would result in true, or a 1.
01010101
11111111
01
You continue down the entire sequence, doing this for each bit:
01010101
11111111
01010101
The bitwise or operator does the same thing, but combines the two numbers with or instead.
In code, these two operators look like this:
int
int
int
int
a = 0b00000101;
b = 0b00000011;
combinedWithAnd = a & b; // results in 0b00000001, or 1.
combinedWithOr = a | b; // results in 0b00000111, or 7.
Bit Fields
279
There’s a third bitwise operator that is the exclusive or operator. (This is sometimes called xor, pronounced
“ex-or.”) The “normal” or operator returns true if either of the two parts are true. This still applies where
both are true. The exclusive or operator is only true if exactly one is true. If they are both true, then it
evaluates to false. The exclusive or operator is the caret symbol (^), which looks like this:
int combinedWithXor = a ^ b; // results in 0b00000110, or 6.
One final, similar operator is the bitwise complement operator (~), which is a little like the ! operator we
saw when we first looked at logical operators. This takes each bit and changes it to the opposite:
int number = 0b11001000;
int bitwiseComplement = ~number; // 0b00110111.
There is a compound assignment operator that you can use for all of these:
int number = 0b00001000;
number <<= 1;
// equivalent of number = number << 1;
number >>= 1;
// number = number >> 1;
number &= 32;
// number = number & 32;
number |= 32;
// number = number | 32;
number ^= 32;
// number = number ^ 32;
Enumeration Flags
While it is possible to work with the byte, int, or another type to accomplish what we’ve seen in this
section, this kind of stuff is typically done with an enumeration in C#, to make the whole thing more
readable. (Enumerations were introduced in Chapter 14.) Before we can use an enumeration for this, we
must add the Flags attribute and assign the right values to each member of the enumeration:
[Flags]
public enum ForumPrivileges
{
CreatePosts =
1 <<
EditOwnPosts =
1 <<
VoteOnPosts =
1 <<
EditOthersPosts = 1 <<
DeletePosts =
1 <<
CreateThreads =
1 <<
Suspended =
1 <<
// Don't forget to add the Flags attribute.
0,
1,
2,
3,
4,
5,
6,
// 1 or 00000001
// 2 or 00000010
// 4 or 00000100
// 8 or 00001000
// 16 or 00010000
// 32 or 00100000
// 64 or 01000000
// Note we can also add in "shortcuts" here:
None = 0,
BasicUser = CreatePosts | EditOwnPosts | VoteOnPosts,
Administrator = BasicUser | EditOthersPosts | DeletePosts | CreateThreads
}
A Practical Example
Before you start thinking, “Well that’s completely useless,” let me show you why these are so useful. The |
operator can be used to “turn on” a bit in the bit field:
ForumPrivileges privileges = ForumPrivileges.BasicUser;
privileges |= ForumPrivileges.Suspended; // Turn on the 'suspended' field
The & operator can be used to check if a particular flag is set, using some trickery:
bool isSuspended = (privileges & ForumPrivileges.Suspended) == ForumPrivileges.Suspended;
The trick is that if we do a bitwise and operation with something that only has one bit set to true, it will
either become all 0’s, which indicates that the field was not set. If the bit was set, we’ll get back the value
of the field we were checking for, turning all other fields to 0.
Using a combination of the & and the ~ operators, we can turn off a particular field:
280
Chapter 43
Other Features in C#
privileges &= ~ForumPrivileges.Suspended;
You can also toggle a field using the ^ operator:
privileges ^= ForumPrivileges.DeletePosts;
Reflection
C# has the ability to inspect elements of executable code, and explore what types are in an assembly.
They can see the methods, properties, and variables it contains, even while your program is running. The
ability to have code analyze the structure of other code is called reflection.
The biggest use for reflection is when you want to look at an unknown assembly or object. Not every
program has a use for this. Most won’t. Doing this is always slower than directly accessing the code.
Having said that, there are still times where it is useful. To name a few examples, a unit testing framework
might want to dig through an assembly to find any method that ends with the word “Test” or have a Test
attribute applied to them, or a plugin system might want to find all classes in a DLL that implements a
specific interface. Reflection can also be used to bend the rules, like calling a private method from outside
the type it belongs to (a great way to shoot yourself in the foot).
The core class used in reflection is the Type class, which represents a compiled type like a class, struct, or
enumeration.
For any given type, you can use the typeof keyword to get the Type object that represents it:
Type type = typeof(int);
Type typeOfClass = typeof(MyClass);
You can alternatively get a type from an object:
MyClass myObject = new MyClass();
Type type = myObject.GetType();
You can figure out what members a type has defined. For example:
ConstructorInfo[] contructors = type.GetConstructors();
MethodInfo[] methods = type.GetMethods();
If you’re looking for a member of the type with a particular name and parameter list, you can do that too:
ConstructorInfo constructor = type.GetConstructor(new Type[] { typeof(int) });
MethodInfo method = type.GetMethod("MethodName", new Type[] { typeof(int) });
In all of these cases, if what you’re looking for doesn’t exist, an empty array or null will be returned.
Once you have the constructor, method, or whatever thing you asked for, you can execute that code with
the Invoke method that they have. Doing so with constructors will return an object that was created by
the constructor, and doing so with a method or property will return the result of the method (or null if
the method’s return type is void):
object newObject = constructor.Invoke(new object[] { 17 });
method.Invoke(newObject, new object[] { 4 });
Using Statements and the IDisposable Interface
Back in Chapter 16 when we first talked about the heap, we talked about garbage collection. One of the
big things that the .NET runtime does for us is get rid of memory that we’re no longer using. Our
program’s memory is managed for us. Occasionally though, the task we’re trying to accomplish requires
Preprocessor Directives
281
using unmanaged memory and resources. When we do this, we can no longer count on garbage
collection to clean up those resources and memory.
There may be times where we create our own unmanaged memory, but it is more common to use a preexisting type that uses unmanaged memory. As an example, when we open a file (Chapter 29) we need to
access unmanaged memory to get to the file system. Back then, we simply used the Close method when
we were done with a file to free unmanaged memory and resources, but there’s a better way.
Typically, types that access unmanaged memory will implement the IDisposable interface. These classes
have a Dispose method which cleans up any unmanaged memory the object is using. We could directly
call this method (in fact, the Close method we used with files does that) but there are some tricky issues
that come up. For instance, if an exception is thrown while the file is open, the Close method may not get
called, leaving the file open. Using what we already know, it is possible to write code to handle this, but C#
provides a simpler way to handle this: the using statement.
A using statement (not to be confused with a using directive) will look something like this:
using (FileStream fileStream = File.OpenWrite("filename"))
{
// Do work here...
}
When the ending curly brace is reached, the object inside of the parentheses in the using statement is
disposed (the Dispose method is called) even if an exception is thrown in the middle of the block.
As you write code, be on the lookout for types that implement the IDisposable interface and dispose of
them correctly when you’re done using them. A using statement like this is a very readable and simple
solution for doing this.
Preprocessor Directives
Many languages, including C#, give you the ability to include instructions for the compiler within your
code. These instructions are called preprocessor directives. (The C# compiler doesn’t have a preprocessor
like some languages, but it treats these preprocessor directives as though there is.)
These preprocessor directives all start with the # symbol, which tips you off to the fact that something is a
preprocessor directive.
#warning and #error
Two simple preprocessor directives are the #warning and #error directives, which give you the ability to
make the compiler emit a warning or error with a specific message. While the compiler typically only
generates errors or warnings when it detects a problem, this allows you to force it to happen.
You’re probably wondering why you’d ever do that, and that’s a great question. I sometimes will put in a
#warning directive to remind myself that I need to still fix something. Because it shows up any time I
compile, it’s unlikely I’d forget about it. (There are plenty of other ways to do this, by the way.)
To add in a #warning or #error directive, you’d simply add something like this to your code:
#warning Enter whatever message you want after.
#error This text will show up in the Errors list if you try to compile.
#region and #endregion
The #region and #endregion directives allow you to add little regions to your code, which Visual Studio
then allows you to collapse and expand. This is one potential way to group related methods or instance
variables (or anything else) within a type, and allow you to collapse and hide each group on the fly.
282
Chapter 43
Other Features in C#
To add a region, you’d do something like this:
#region Any region name you want here
public class AwesomeClass
{
// ...
}
#endregion
With a region defined, you can collapse and expand the entire region with the outlining feature in Visual
Studio. This can be done by clicking the little ‘+’ and ‘-’ icons on the left hand side of your code:
You can nest regions.
#if, #else, #elif, #endif, #define, and #undef
There is a whole collection of other useful compiler directives, but before I get into them, I need to discuss
compilation symbols briefly. Compilation symbols are special names that can be defined (turned on) or
undefined (turned off—the default). For example, there’s a DEBUG symbol that is defined when you
compile in debug mode, but not when you compile in release mode. You can define your own symbols,
which I’ll explain in a second.
The #if, #else, #elif, and #endif directives work very much like an if statement, except they’re
instructions for the compiler to follow, and don’t end up in your final program. You could add the
following to your program, and the parts between the #if and #else only get compiled if DEBUG is
defined.
public static void Main(string[] args)
{
#if DEBUG
Console.WriteLine("Running in debug mode.");
#else
Console.WriteLine("Running in release mode.");
#endif
}
If you’re running in debug mode, the compiled program would be equivalent to this:
public static void Main(string[] args)
{
Console.WriteLine("Running in debug mode.");
}
And of course, if you’re running in release mode, it’s this:
public static void Main(string[] args)
{
Console.WriteLine("Running in release mode.");
}
Despite this example, it is dangerous to have things run differently in the final release version than it does
in testing, because you may not discover all of the bugs it has until the program has been released.
#elif is short for “else if” and can be used to chain together a sequence of conditional blocks.
Nullable Types
283
You can use the && and || operators with symbols as well.
You can also define a symbol with #define, and undefine it with #undef, right at the top of a file:
#undef DEBUG
#define MAGIC
This block would “undefine” or unset the DEBUG symbol, and define your own special MAGIC symbol.
Since you can’t determine the ordering of files, using #undef and #define only apply to a single file.
Alternatively, you can define a symbol for the entire compilation process (much like how DEBUG will be
set in every file, unless you turn it off with #undef). To do this, right-click on your project in the Solution
Explorer and choose Properties. Select the Build tab, and at the top, under Conditional Compilation
Symbols, enter the list of symbols you wish to define for all files, separated by spaces.
Nullable Types
Value types are not allowed to be assigned a value of null. This is a fundamental feature of value types,
but there are times where you wish you could bend the rules a little. For example, bool is a value type and
can only store true and false. But occasionally, we have a need to represent true, false, or unknown.
The concept of nullable types addresses this. A nullable type is simply one that uses the Nullable<T>
generic type:
Nullable<bool> nullableBool = true;
nullableBool = null;
// We can assign true or false...
// but also null.
Even better, C# provides a shorthand way of doing this:
bool? nullableBool = true;
You can use the HasValue property to figure out whether the nullable type is null or contains a real value,
and you can use the Value property to grab the value, assuming that HasValue is true:
if(nullableBool.HasValue)
bool actualValue = nullableBool.Value;
You can use the null-coalescing operator (??) to assign a default or fallback value if a nullable type is null.
(The null-coalescing operator works on null references as well.)
bool actualValue = nullableBool ?? false;
The actualValue variable will contain the value of nullableBool if it has a value, or if it’s null, it will be
false.
Nullable types can’t be created from reference types, but that’s OK because they already support null.
It is interesting to note that while Nullable<T> nominally makes value types be able to have a value of
null, Nullable<T> is still a value type, and can’t even contain a null reference itself. Instead, it associates
an additional bool value with the rest of the data. If this is extra value is true, then the normal value is
assumed to be good. If it’s false, then Nullable<T> will throw exceptions when trying to access the data.
The ability to treat it as though it can take on a null value is just compiler magic.
Simple Null Checks: Null Propagation Operators
C# 6.0 introduced another new feature called null propagation, succinct null checking, or the null propagator
operator that is quite powerful. One of the greatest annoyances of programming in many programming
languages, especially object-oriented languages like C# is null checking.
284
Chapter 43
Other Features in C#
Let’s say we have a HighScoreManager class that has a Scores property that contains an array of items in
order from highest to lowest. Each score is represented with an instance of the HighScore class, which
has a reference to the player that got the score, which has the player’s name. (I know, that’s kind of a
mouthful.) To get the player’s name that has the highest score, your code might look like this:
private string GetTopPlayerName()
{
return highScoreManager.Scores[0].Player.Name;
}
That’s all fine and good until you realize that there are five things in this statement that might be null. If
any of those pieces are null, you’ll get a NullReferenceException, which could bring your program to a
crashing halt and veiled threats about sending you on a “vacation” to the Spice Mines of Kessel. (Some
users take their crashes a little too seriously.)
The point is, checking for null in your code is a good idea. If there is any chance that something could be
null, checking to make sure it’s not is good defensive coding practice.
So what does that do to our code?
private string GetTopPlayerName()
{
if(highScoreManager == null) return null;
if(highScoreManager.Scores == null) return null;
if(highScoreManager.Scores[0] == null) return null; // Could still fail if empty.
if(highScoreManager.Scores[0].Player == null) return null;
return highScoreManager.Scores[0].Player.Name;
}
Ouch. That’s a ton of code just to make things safe for use. That’s four whole lines of null checking!
C# 6.0 introduced two new operators called the null propagator operators (or simply, the null propagators)
that makes it easy to do these null checks without causing your code to get ugly. These two operators are
closely related, and are the conditional member access operator (?.) and the conditional indexer access
operator (?[]). The following shows the conditional member access operator in action:
return highScoreManager?.Scores[0].Player.Name;
This operator checks if the thing before it was null. If it was, it simply evaluates to a null result. If not, it
continues on and evaluates whatever is beyond the operator. You could say that this line of code turns
into the following:
HighScoreManager manager = highScoreManager;
if(manager == null) return null;
return manager.Scores[0].Player.Name;
That’s one null check down. Four more to go. We can string these together to get the result we want:
return highScoreManager?.Scores?[0]?.Player?.Name;
This shows us the conditional indexer access operator (the part that says Scores?[0]), and includes more
conditional member access operators through the whole statement.
With all of these null propagators, if any of the objects involved (immediately before a null propagator)
turn out to be null, the expression will short circuit and evaluate to null. Otherwise, it will keep going down
the chain and evaluate the next part.
There are three nuances with these null propagators that deserve more discussion.
Command Line Arguments
285
First, when you use a null propagator, it actually copies the object before it into a new variable. (The
earlier code that shows what a null propagator turns into illustrates this by copying highScoreManager
into the local manager variable.) This prevents situations where a value might become null after the null
check, but before using it. This can happen in a multi-threaded application if the variable in question is
shared by multiple threads. We make a local cached copy instead, and use that afterwards.
Second, when these operators “fail,” they produce a null value. This is fine if you’re working with a
reference type, because those can handle a null value just fine. But it causes problems if we’re using a
value type. Imagine if we were trying to get the score (an int) instead of the name, as shown below:
int? score = highScoreManager?.Scores?[0]?.Score;
Because the null propagators might return null, this expression doesn’t evaluate to a plain int, but rather
a nullable int. (We discussed nullable types in the previous section.)
If we want to make sure it gets turned back into a non-null value, we would simply use the null-coalescing
operator (??) from the previous section to turn nulls into some other specific value:
int score = highScoreManager?.Scores?[0]?.Score ?? 0;
In this case, we either get the score we wanted or we get the fallback value of 0 if we encounter any nulls.
The third point of note is that while there is a ?. and a ?[] null propagator, there is no ?() operator. The
following won’t work:
Func<string, string> delegateMethod = null;
string resultOfMethod = delegateMethod?("3");
// Invalid...
The workaround is to invoke the delegate with the Invoke method, instead of directly invoking the object:
Func<string, string> delegateMethod = null;
string resultOfMethod = delegateMethod?.Invoke("3");
// Valid...
Here is a second example with an event:
public class SimpleClassWithAnEvent
{
public event EventHandler<EventArgs> SomethingHappened;
public void OnSomethingHappened()
{
SomethingHappened?.Invoke(this, EventArgs.Empty);
}
}
This is actually a better and more concise way to write our event raising code than we learned about in
Chapter 33, because it handles the situation where the event itself is just about to become null. (The ?.
operator makes a local copy to protect against that exact scenario.)
Command Line Arguments
Not all programs have a user sitting in front of them typing commands or pushing buttons. A second
option is to allow the program to take command line arguments, supplied to the program at the time of
launch. This allows you to put your program in scripts, and run it as a part of a larger automated task.
Let’s say you have a program called Add.exe that adds two numbers together. Instead of asking the user
to type in two numbers, as an alternative, the user can specify those numbers on the command line when
they start the Add.exe program. From a command prompt, this might look like this:
C:\Users\RB\Documents>Add.exe 3 5
286
Chapter 43
Other Features in C#
These values on the end are pulled into your program as command line arguments. If you look at your
Main method, you’ll see that it has a string[] args parameter. Command line arguments arrive in this:
static void Main(string[] args)
{
int a = Convert.ToInt32(args[0]);
int b = Convert.ToInt32(args[1]);
Console.WriteLine(a + b);
}
User-Defined Conversions
Way back in Chapter 9, we looked at typecasting, which allows you to do things like convert a float into a
double, or a double into a float, even though they’re different types.
We briefly discussed implicit casting and explicit casting. Implicit conversions happen without asking,
usually from a narrower type to a wider type. (For example, from int to double, because a double can
store anything an int can store.) Explicit conversions do not happen automatically, but do allow for
conversion between two types with the typecasting operator: int a = (int)3.14;
Casting works great for the built-in types. It also works well in an inheritance hierarchy. But C# also allows
you to define conversions between types you’ve defined. Let’s say you’ve got a simple class like the
MagicNumber class below, where you have basically a number with an extra property:
public class MagicNumber
{
public int Number { get; set; }
public bool IsMagic { get; set; }
}
If you want to convert this to and from an int, you could always add methods to do this like this:
public int ToInt()
{
return Number;
}
public static MagicNumber FromInt(int number)
{
return new MagicNumber { Number = number, IsMagic = false };
}
This works, but isn’t nearly as clean as we’ve seen with conversions between the built-in types. We can fix
that by defining our own conversion operators:
public static implicit operator MagicNumber(int value)
{
return new MagicNumber() { Number = value, IsMagic = false };
}
This is defined in essentially the same way as any other operator (Chapter 34). Like with all other
operators, this must be static and public.
For a user-defined conversion, we must specify whether it is implicit or explicit. In this case, we’ve
chosen implicit. The “name” of the operator will be whatever we are converting to. The body of the
custom conversion does whatever work we need to do to produce the transformed value. With this added
to our MagicNumber class, we can simply convert from ints to MagicNumbers:
int aNumber = 3;
MagicNumber magicNumber = aNumber;
The Notorious ‘goto’ Keyword
287
We can also create an explicit cast in the same way. The following code defines an explicit cast from our
MagicNumber type to int. (Any time we lose information in a conversion, we should always use an
explicit cast.)
static public explicit operator int(MagicNumber magicNumber)
{
return magicNumber.Number;
}
Now, elsewhere in our code, we can use this conversion, though this time, we’ll need to use a typecast:
MagicNumber magicNumber = new MagicNumber() { Number = 3, IsMagic = true };
int aNumber = (int)magicNumber;
One gotcha with user-defined conversions is that they will always produce a new, separate instance. You
can see the new keyword even popping up in our earlier implicit cast. The trick is that it’s not always
obvious that we’ve produced a new object, especially when the cast is implicit. For example:
TypeA a = new TypeA();
TypeB b = a; // Converted with an implicit user-defined conversion
b.DoSomething(); // Affects b, but not a, because b is a different object because of the implicit cast.
Because of weird errors like this, it may be easier to just use ToWhatever() methods (a la ToString(),
which everything has). When you do this, it is much easier to see that you’re working with a separate
object:
TypeA a = new TypeA();
TypeB b = a.ToTypeB();
b.DoSomething();
User-defined conversions have their place, but it is always also worth considering if defining
ToWhatever() methods or adding new constructors to do the conversion might be better in any
particular case.
The Notorious ‘goto’ Keyword
C# has a goto keyword that allows the flow of execution to jump or “go to” an arbitrary other location in a
method to continue execution. Before I go further, I should caution you that the second you mention
using goto, you will literally have programmers crash through your wall yelling, “Don't use goto!”
Give it a try. When you’re near some other programmers, look at your code studiously for a few seconds,
scratch your chin, and say, “Maybe I'll use a goto here.”
As we’ll soon see, there are good reasons that programmers react so quickly and so harshly to goto. But if
C# had hand grenades in it, I'd still want to teach you about C# hand grenades, so that if you stumble into
a live one, you understand it well enough to react to it. For the same reason, I'm going to cover goto here.
How ‘goto’ Works
Conceptually, goto is pretty straightforward. It is a statement that causes the flow of execution to jump to
another location, with some limitations.
A goto requires two parts:
1.
2.
A label, or a labeled statement. These are the targets for a goto statement.
A goto statement that identifies which label to jump to.
288
Chapter 43
Other Features in C#
The following code is slightly complicated, but that's where you usually see goto. “Slightly complex” is
goto’s natural habitat.
/// Look for one number in a multi-dimensional array of numbers.
public void FindNeedleInHaystack(int[,,] haystack, int needle)
{
int locationX, locationY, locationZ;
for(int x = 0; x < haystack.GetLength(0); x++)
{
for(int y = 0; y < haystack.GetLength(1); y++)
{
for(int z = 0; z < haystack.GetLength(2); z++)
{
if(haystack[x, y, z] == needle)
{
locationX = x; locationY = y; locationZ = z;
goto Found;
}
}
}
}
goto End;
Found:
Console.WriteLine($"Found at {locationX}, {locationY}, {locationZ}.");
End:
;
}
This block of code illustrates all of the major mechanics of goto. In the heart of those nested loops is a
goto statement, which identifies the label to jump to. (Found, in this case.) If that line is reached, the flow
of execution will immediately leave where it is at and jump directly to the label it references: the Found:
label towards the bottom.
This code also has a second goto. If the flow of execution never gets to that first goto, it will eventually
complete the search and fall out of the loops normally. To skip the “Found at” message, the code includes
another goto statement to skip past it to the end of the method.
Here are a few more of the finer points of goto:





Labels can be any legal identifier. It has the same restrictions as any variable or method name.
A label must always precede a statement. If nothing else, you can put an empty statement (a
lonely semicolon) as shown after the End label.
Encountering a label does not interfere with the flow of execution. goto Something; jumps you to
the location of the label, but encountering a Something label does not.
A goto can only take you up or down within the current scope (as is shown with goto End;), or up
to a parent scope (as is shown with goto Found;). It cannot take you into a child scope, or into a
different child scope of a parent (for example, out of one for loop and into another).
A goto cannot take you between methods.
This code illustrates an example of where goto has potential value. Breaking out of multiple loops without
a goto is not exactly trivial. That doesn't necessarily mean you should use it here. There are some definite
good reasons to avoid goto here or anywhere. But if there were ever a time for a goto statement, this
would be a good candidate.
The Notorious ‘goto’ Keyword
289
Why You Should Avoid ‘goto’
There is probably no programming concept that will evoke such an extreme negative reaction as using
goto. But what makes it so bad?
The answer is that goto is that it has a profoundly negative effect on code readability and maintainability.
The earlier code sample is probably goto at its best. Here's an example of goto at its worst:
public void FindNeedleInHaystack(int[,,] haystack, int needle)
{
int x = 0;
Top:
int y = 0;
TryAgain:
int z = 0;
Next:
if (z >= haystack.GetLength(2))
{
y++;
goto TryAgain;
}
if (haystack[x, y, z] == needle) goto Found;
z++;
goto Next;
y++;
if (y < haystack.GetLength(1)) goto TryAgain;
x++;
if (x < haystack.GetLength(0)) goto Top;
goto End;
Found:
Console.WriteLine($"Found at {x}, {y}, {z}.");
End:
;
}
Pop Quiz: Does this code have the same functionality as before?
It is supposed to. I just unrolled the loops into goto statements, but in theory, kept the same functionality.
But I’m having a hard time verifying that it still produces the correct results. And I wrote the thing!
The massive drop in readability is also a maintainability killer. Once a goto has been added, it tends to
become impossible to extract. It's like a tick or a leech. One does not simply remove it.
Furthermore, gotos are contagious. One goto begets more gotos. In the original goto code, the mostly
reasonable goto Found; produced a second less reasonable goto End;. This is commonplace with gotos.
The above code is an especially bad example, but once goto pops up, it morphs into this pretty quickly.
The reality is, by applying refactorings like extracting pieces into methods and just reorganizing confusing
code, you can always rewrite goto code in a way that doesn’t require goto. And nearly every experienced
developer will tell you that taking this approach will save you a lot of pain in the long term.
290
Chapter 43
Other Features in C#
Many teams will just simply forbid goto in their codebase. It’s probably for the better.
At a minimum though, I would strongly recommend pretending goto doesn’t even exist for at least the
first year of your development life. That will get you practice with different (better) approaches and make
you far wiser before you start unknowingly inflicting damage on your code quality.
Generic Covariance and Contravariance
When you have inheritance and you mix derived types with base types, the rule is that you can substitute
the derived class any time the base class is expected. As a frame of reference for this discussion, let’s
assume we have the following two classes, which form a small inheritance hierarchy:
public class GameObject // Any object in the game world.
{
public float X { get; set; }
public float Y { get; set; }
}
public class Ship : GameObject
{
public void Fire() { /* ... */ }
}
Recall that you can assign an instance of the derived class to a variable whose type is the base class:
GameObject newObject = new Ship();
Or consider the method below, which expects a GameObject:
void Add(GameObject toAdd) { /* ... */ }
You can invoke this with the derived object:
Add(new Ship());
The derived class can be used any time that the base class is expected, because the derived class is, in
fact, also the base type. A Ship is a special type of GameObject, but that makes it a GameObject too.
Generics and the Type Hierarchy
This substitution relationship between derived and base classes is a useful one, but generics complicate
the picture. While you can do this:
GameObject newObject = new Ship();
You cannot do this:
List<GameObject> newObjects = new List<Ship> { new Ship(), new Ship(), new Ship() };
While there is a relationship between GameObject and Ship, there is not an equivalent relationship
between List<GameObject> and List<Ship>. The hierarchy relationship is not magically bestowed on
generic types that utilize the types in the hierarchy. List<T> is instead derived from plain old object.
In fact, if this were allowed, it would cause serious problems. Consider the consequences of this code:
List<GameObject> objects = new List<Ship>();
objects.Add(new Asteroid());
That second line would seem reasonable (assuming Asteroid is another class derived from GameObject).
We're simply adding an Asteroid to a collection that can hold any type of GameObject. Yet the actual List
type being used is a list of Ship. You shouldn’t be able to put Asteroid in a list of Ship.
Generic Covariance and Contravariance
291
But still, sometimes it could be nice to allow generic types to leverage the relationships of their type
parameters.
Variance: Defining Hierarchy-Like Relationships with Generic Types
In C#, you actually have control over how the hierarchy relationship transfers over to a generic type that
uses it. The rules that govern if and how this relationship is applied is called variance.
If you remember from Chapter 26, whenever you define a generic type, you specify a list of generic type
parameters that can be used elsewhere in the class. For example, in the code below, we have a generic
interface (called IGeneric) with two generic type parameters that called T1 and T2:
public interface IGeneric<T1, T2> { /* ... */ }
For each of these generic type parameters, you can specify what form of variance you want it to use.
There are three variance options available.
The first is invariance, and is the default. This means that the type hierarchy relationship is ignored
entirely. This is what we saw with how List<Ship> could not be supplied when List<GameObject> was
required. When something is invariant, you must use the exact type specified.
The second is covariance. This means that the type hierarchy relationship is preserved. If you have a
covariant generic type parameter, then you could assign a derived version when the base version is
specified, which we might have hoped for with List<T>, had it not caused problems:
Covariant<GameObject> thing = new Covariant<Ship>();
Covariance only works if the generic type parameter is used solely as output from the class. That is, it is
used as return values for methods (including property getters and indexers). It cannot be used as input to
the class (the type of a method parameter or a setter for a property or indexer). This is exactly the reason
why List<Ship> can't be stored in a List<GameObject>. The Add method requires passing in something
of type T, so it can’t be covariant.
On the other hand, IEnumerable<T> is covariant. IEnumerable<T> was introduced in Chapter 25. This is
an interface that allows you to simply iterate over a collection, or run through a collection and view it, one
item at a time. Because IEnumerable<T> doesn't define any methods that require the generic type as
input, it can be covariant.
Also, since List<T> implements the IEnumerable<T> interface, the following is possible:
IEnumerable<GameObject> gameObjects = new List<Ship> { new Ship(), new Ship(), new Ship() };
A List<Ship> implements IEnumerable<Ship>, which is assignable to IEnumerable<GameObject>.
The third option is contravariance. While covariance preserves the type hierarchy relationship,
contravariance actually inverts it. If a generic type parameter is contravariant, we can do this:
Contravariant<Ship> thing = new Contravariant<GameObject>();
This might seem strange at first, but it is related to the fact that contravariance can only work if the
generic type argument is only used on inputs to the class.
The best practical example of contravariance is the Action<T> delegate type that we introduced in
Chapter 32. Action<T> is a delegate type that matches any method that has a single parameter of the
generic type T and a void return type. For example, if we had a delegate that looked like this:
Action<Ship> processShipDelegate;
292
Chapter 43
Other Features in C#
We could assign either of these methods to it:
public void LogShip(Ship s) { /* ... */ }
public void DrawShip(Ship s) { /* ... */ }
But Action<T> is contravariant. That means that we could also assign these to it:
public void LogGameObject(GameObject o) { /* ... */ }
public void DrawGameObject(GameObject o) { /* ... */ }
The type isn't a perfect match, but contravariance allows Action<GameObject> (which these are) to be
assigned to Action<Ship>.
But contravariance will only work if the generic type parameter is solely used for inputs.
Based on our earlier example, you can start to get a feel for how and why that works. If we assign void
DrawGameObject(GameObject o) to that processShipDelegate variable, we can then turn around and
invoke it with a Ship:
processShipDelegate = DrawGameObject;
processShipDelegate.Invoke(new Ship());
We’re calling a method that expects the base type (GameObject) but passing in the derived type (Ship).
Specifying Variance in Code
We’ve covered how variance works at a conceptual level, as well as some examples of how to actually use
covariant and contravariant generic type parameters. Now it’s time to look at how to actually make
something of your own creation covariant or contravariant.
Variance can be specified on generic interfaces and delegate types. (Notably not on generic classes.)
To specify variance, you simply put the out (for covariance) or in (for contravariance) keywords just before
the generic type parameter where you define the type:
public interface IVariance<out TCovariant, in TContravariant>
{
TCovariant ProduceAValue();
void ConsumeAValue(TContravariant value);
}
While these keywords don’t do a great job at helping you remember the terms “covariant” and
“contravariant,” they do a good job of helping you remember where they can be used.
If you’ve marked something as covariant with the out keyword, the compiler will enforce that it is only
used as an output. If you attempt to use it as an input, it will not compile. The same applies with
contravariance and the in keyword. If you make something contravariant, but then try to use it as a return
value, the compiler will catch it.
Invariance is the default. If you leave in or out off, then the generic type parameter will be invariant. You
will be able to use it as both an input and an output, at the cost of losing variance.
It is good practice to make types covariant or contravariant if the intent is to only use it as input or output.
Mixing and Matching
Every generic type parameter is allowed to have its own variance. You could have five generic type
parameters, one of which is invariant, two of which are covariant, and two that are contravariant.
You don’t have to look far in the Standard Library to find a type that does some mixing and matching.
Recall that the Func family of delegates specifies some number of generic inputs and a generic return
value. For example, there is this one:
Advanced Namespace Management
293
public delegate TResult Func<in T1, in T2, in T3, out TResult>(T1 arg1, T2 arg2, T3 arg3);
The three generic type arguments that are used as inputs (T1, T2, and T3) all are marked with that in
keyword, making them contravariant. The one that is used as an output (TResult) is marked with out,
making it covariant.
To further illustrate the variance point, what this means is that if you have a variable that looks like this:
private Func<Ship, Ship, Ship, GameObject> shipMuxer;
You can assign it any of the following functions as a value:
private
private
private
private
GameObject DoStuff(Ship a, Ship b, Ship c)
Ship ReturnAShip(Ship a, Ship b, Ship c) {
GameObject ExpectGameObjects(GameObject a,
Ship ExpectObjectsAndReturnAShip(object a,
{ /* ... */ }
/* ... */ }
GameObject b, GameObject c) { /* ... */ }
object b, object c) { /* ... */ }
Advanced Namespace Management
In Chapter 27 we discussed namespaces in depth. There are a few weird corner cases that we ignored in
that chapter, simply because they have few practical uses. But since they are a part of the language, these
corner cases are worth a brief mention here in this chapter.
In certain cases where names of classes and namespaces get reused, the C# compiler may get confused
about what you're referring to. For example, while we're familiar with the Console class in the System
namespace, imagine if you made a class called System that had a method called Console in it. At this
point, System.Console is ambiguous.
The first rule here is, “Don’t do that.” Avoid name collisions between classes and namespaces whenever
possible. It's not just confusing to the compiler, but to humans as well. So avoid it wherever possible.
On the other hand, sometimes the mess was caused by the exact wrong combination of third party
libraries, and you have no choice.
To resolve these scenarios, use the global keyword with the namespace alias operator (::) shown below:
global::System.Console.WriteLine("Hello World!");
The global:: indicates that the compiler should start searching for names (namespaces or type names)
starting all the way up at the top of the system. (This top level is called the global namespace.)
On a related note, there are a few occasions in which you need to reference two versions of the same
assembly or dependency. This, too, should be avoided whenever possible.
But when it's unavoidable, the way to deal with it is with an extern alias. An extern alias allows you to bring
in the code in a DLL under a separate “root” namespace, alongside the global namespace, with a different
name of your own choosing.
To do this, you must do two things. First, you must apply a namespace alias to the DLL that you're
referencing. This is not hard to do:
1.
2.
3.
In the Solution Explorer, in your project, under the References element, find the assembly that
you want to give an alias to.
Right-click on it and select Properties.
Look for the property called Aliases. By default, this will say “global”. Simply change this to
another value. For example, “MyAlias”.
In code files that want to use this alias, put code similar to the following at the very top of the file:
294
Chapter 43
Other Features in C#
extern alias MyAlias;
Note that extern aliases must go before anything else in the file, including the rest of your using
directives.
At this point, everything in your new aliased namespace is accessible like you’re used to, with your alias
prepended to the front. So you can add using directives for it, reference types in it with fully qualified
names, etc.
Additionally, you can access stuff in this namespace using the namespace alias operator (::) as well:
MyAlias::My.Namespace.MyClass. This helps illustrate that your extern alias is not under the global
namespace, but is at the root level beside it.
Remember: these are situations that are best avoided. If you have a different way around needing to use
the global:: namespace reference or extern alias, you should. But it is good to know that things are in
place to work through the problem if it becomes unavoidable.
Checked and Unchecked Contexts
In Chapter 9 we discussed overflow errors. To recap, when you do some math that causes your integral
numbers to go beyond their allowed limits, you run into overflow. By default, the value is “truncated” and
the number wraps around. For example, the following code outputs -2147483648, which is int.MinValue.
int x = int.MaxValue;
x++;
Console.WriteLine(x);
The code above, by default, wraps around when we exceed the maximum value. (Note that this does not
apply to floating point numbers. They just become infinity instead.)
Most scenarios won’t push you into overflow at all. If it’s a threat, you upgrade to a larger data type, like
using long instead of int, or int instead of short. In other cases, if you do wrap around, it’s not that big of
a deal. But in some cases, wrapping around causes huge problems. Suddenly the number goes from a big
number to a small one. If that’s the balance of your bank account, you’re going to be sad that it changed
like that without you even being told.
This is because by default, C# code runs in an unchecked context. That is a way of saying the runtime
doesn’t care if something overflows. It doesn’t even check for it. It just allows the bits to be reinterpreted
as the truncated value.
On the other hand, you can specify in your C# code that some math operation should be ran in a checked
context instead.
int x = int.MaxValue;
checked
{
x++;
}
Console.WriteLine(x);
If you do this, then instead of wrapping around, an OverflowException will be thrown. When this
happens, you can optionally catch it and handle it however you feel you need to, as we discussed back in
Chapter 30.
Likewise, you can also specify that a section of code should always be done in an unchecked context:
int x = int.MaxValue;
unchecked
Volatile Fields
295
{
x++;
}
Console.WriteLine(x);
Unchecked is the default. So why have a keyword that specifies this?
It turns out you can set the entire application to be either checked or unchecked by default by setting an
option on the compiler. If you have set up the application to be checked by default, then being able to
specify that a certain computation must be unchecked is useful.
To specify that an application (or a single project) should be checked, you can either add the /checked+
compiler command line argument (or /checked- to turn it off) or if you prefer to do this in the UI itself:





Right-click on a particular project in your Solution Explorer.
Choose Properties.
Select the Build tab.
Push the Advanced button.
In the Advanced Build Settings dialog that comes up, check or uncheck the box that is labelled
“Check for arithmetic overflow/underflow”.
Whether something is checked or unchecked is not usually something you need to worry about. Usually
you don’t care because you won’t run into a place where overflow can even happen or you don’t care that
it wraps around as the default behavior. But in cases where it matters, it is nice to know that you have
options to control it.
Volatile Fields
In this section, we are going to talk about C#'s volatile keyword and some related concepts that help to
paint a picture of what it does and why.
This topic is another one of those dark corners of C#, and of programming in general.
Program Order and Out-of-Order Execution
The first point of interest here is to describe two closely related concepts called program order of
instructions and out-of-order execution. When you look at a chunk of source code like the following, you
make the assumption that things will happen in the order you see it:
int x = 3;
x += 7;
int z = 10;
This is a key tenet of procedural programming. Things happen one after the next.
As it so happens, sometimes either the compiler or the hardware itself is able to see that it can get more
things done faster if it can rearrange the order of some of the instructions, or run the instructions in
parallel. The last line in the above code is a good example of this. The int z = 10; could actually be
completed (or at least started) before the x += 7 completes.
The value in allowing the hardware to rearrange instructions or interleave instructions is clearer when you
recall that each line of C# code that you write tends to compile down to multiple binary instructions.
The ability for hardware to reorder instructions is called out-of-order execution. It is a key way that
hardware can run code more quickly. When performing out-of-order execution, the hardware still
guarantees that any changes it makes will still appear as though it were executed in program order.
296
Chapter 43
Other Features in C#
Here’s the catch: the hardware generally makes the assumption that only a single thread is running. When
two or more threads (or processes) share memory, this out-of-order execution can occasionally cause
problems. To illustrate, consider the following two chunks of code.
Thread #1 is running this:
value = 42;
complete = true;
And Thread #2 is running this:
while(!complete) { }
Console.WriteLine(value);
What will Thread #2 write out?
Under normal rules, you would assume that it prints 42. But because of out-of-order execution, it is
possible that the ordering in Thread #1 could get reversed and something besides 42 could appear
instead.
In this context, you may hear people talking about the results of one memory operation (a read or a write)
being “visible” to other threads. You could say an operation is visible if, from the perspective of all threads
that have access to the memory location, all changes to the memory it affects have completed.
In our code above, it's possible that setting complete to true might be visible before the change of value
to 42, at least from Thread #2’s perspective.
Memory Barriers
Obviously, with a problem like what we’re describing, you'd assume that there's a solution of some sort.
The answer here is something called a memory barrier. A memory barrier is a special instruction at the
hardware level that indicates that pending reads or writes must complete before continuing past the
memory barrier. There are two ways that this can work:
1.
2.
Acquire Semantics: If an operation has acquire semantics, then it will be visible before later
instructions.
Release Semantics: If an operation has release semantics, then earlier instructions will be visible
before it.
Creating Volatile Fields
In C#, fields (both static fields and instance variables) can be marked with the volatile keyword. The
volatile keyword is intended to help sort out issues that come up when multiple threads have access to
the field, and you're not using another mechanism for thread safety (like a lock).
In particular, reading from a volatile field is a “volatile read,” which means it has acquire semantics. It is
guaranteed to occur before any references to memory that occur after it in program order.
Additionally, writing to a volatile field is a “volatile write,” which means it has release semantics. It is
guaranteed to occur after any references to memory that occur before it in program order.
If we defined our original complete variable like this:
private bool complete;
We could make it volatile like so:
private volatile bool complete;
This now means that when Thread #1 runs this code:
Volatile Fields
297
value = 42;
complete = true;
We can guarantee that complete won’t be set to true until after value = 42; has completed, from the
perspective of any thread. That fixes our issue.
So when should you use volatile?
If you only have one thread, you do not need volatile, and shouldn’t use it for performance reasons. You
also don’t need it if the variable is always accessed through some other thread-safe mechanism.
But any time where two threads might access it without another thread-safe mechanism, you will likely
want to make the field volatile.
Part
5
Mastering the
Tools
In order to master C#, you need to thoroughly understand the development tools that you use. We’ve
spent the bulk of this book looking at how to write C# code, but in order to truly understand how a
program works, you’ll need to learn the details of how C# and the .NET Platform function, and how to
use Visual Studio effectively. In this section, we’ll look in depth at how these things work, and we’ll pay
particular attention to how to debug your code and fix common problems.
We’ll cover:






The .NET Platform in more depth (Chapter 44).
A detailed look at Visual Studio and useful features it has (Chapter 45).
How to work with multiple projects and dependencies, including NuGet (Chapter 46).
Dealing with common compiler errors (Chapter 47).
How to debug your code (Chapter 48).
A behind-the-scenes guide through the way Visual Studio organizes and manages your code
and other assets in a project or solution (Chapter 49).
44
44 The .NET Platform
In a Nutshell






The .NET Platform is the system on which C# runs.
C# code is compiled to CIL instructions by the C# compiler, and to binary instructions at runtime by the JIT compiler.
The Common Language Runtime is an application virtual machine that runs your program.
The .NET Standard Library defines the collection of types that are available for you to reuse in
your program.
There are several stacks that you can utilize that run on different hardware platforms and
operating systems:
 The .NET Framework is the oldest and most popular, and runs on Windows devices.
 The .NET Core is the newcomer, and can run on macOS, Windows, and Linux.
 The Xamarin stack primarily targets mobile devices (iOS and Android).
The .NET Platform also contains app models for creating specific application types (desktop
apps, web, mobile apps, games, etc.).
The .NET Platform, often called the .NET Framework or simply “.NET”, is the system that your C#
applications run on. The .NET Platform has a lot of interconnected pieces with an interesting history. To
make sense of this, we’ll start with an overview of what the .NET Platform is. Then we’ll cover a brief
history of .NET, which shows how we got where we’re at and shed some light on where it’s heading. We
will then go through the individual pieces that comprise the .NET Platform in more depth.
Overview of the .NET Platform
The .NET Platform, also called the .NET Framework (though we’ll draw some distinctions between those
two terms later) is a vast software platform, designed to make software development faster and easier.
The goals is to let programmers focus on the unique aspects of their program, without needing to worry
about “boilerplate” code that is common to all applications.
The .NET Platform architecture diagram below shows the current state of the .NET Platform. This has been
evolving at a fast rate, and will continue to change in the future.
302
Chapter 44
The .NET Platform
The .NET Platform can be thought of as three different layers. The bottom layer is a collection of shared
infrastructure. This includes the languages themselves (the specification for each language) the compilers
for the languages, the Common Intermediate Language (CIL) that the compilers emit, the Common
Language Runtime (CLR) that runs .NET applications, along with supporting tools and components.
Built on top of that is the .NET Standard Library (often called the Base Class Library, though the two are
subtly different). This is a vast collection of reusable code that takes care of common tasks or solves
common problems, including things like networking, file system access, and collections. Your C#
Overview of the .NET Platform
303
application (or any other .NET language) can use anything in The Standard Library. It is common code that
any type of application—web, desktop GUI, console, game—can utilize.
You can see that the top level is split into three different sections. Each of these can be called a different
stack, flavor, or implementation of the .NET Platform. Sometimes, each of these are even called a .NET
platform (contrasted with The .NET Platform with a capital ‘P’, which is the label I’m applying to the entire
ecosystem, including all of the stacks).
The three stacks shown in the diagram are not meant to be a complete list of all possible flavors of .NET,
though these are the three biggest and most widely used stacks. Each of these stacks represent a unique
configuration of .NET. Some of the infrastructure pieces (like the Common Language Runtime or the C#
compiler) are swapped out for compatible replacements. In different stacks, even the .NET Standard
Library can be swapped for a completely different implementation.
Each stack is carefully crafted for different unique purposes. For instance, the .NET Framework is the
biggest and oldest of the stacks. It’s primarily suited to Windows machines. The .NET Core stack is much
newer and much smaller, but can also target Linux and Mac. The Xamarin stack is aimed primarily at
mobile development, including iOS and Android.
Each stack supports a number of different app models. An app model is primarily a library dedicated to
building some specific type of application. For example, the Windows Presentation Foundation (WPF) app
model is a framework for building desktop GUI applications. ASP.NET is a framework for building web
applications (generally in conjunction with JavaScript, CSS, and HTML on the front-end). In addition to
providing a library of code specific to a certain type of application, app models can also provide
infrastructure as well: things like security models and deployment models.
Terminology: .NET Platform and the .NET Framework
The terminology here gets a little confusing. This is because of history. Out of all of the stacks, the .NET
Framework was the first. Because of this, other stacks have also been frequently called implementations
of the .NET Framework. You might hear people say, “.NET Core is an implementation of the .NET
Framework that also runs on Linux and OSX.” The term “.NET Framework” then basically has three
definitions that depend on context:
1.
2.
3.
A label applied to the entire ecosystem (what I’m referring to as the .NET Platform in this book).
The specific .NET Framework stack, excluding .NET Core, Xamarin, and other stacks.
A template of what a stack should look like, of which, .NET Core, Xamarin, etc. simply reimplement.
The Internet and other material will use all three of these definitions at different times. When you come
across the term, you should stop and think about which of these definitions the author is referring to. In
this book, I will always call #1 either “.NET” or “The .NET Platform”, with a “The” at the front and a capital
“P”. When I say “.NET Framework”, I’m always using definition #2: the specific .NET Framework stack. And if
I mean #3, I will refer to them simply as stacks, or possibly .NET platforms, with a lowercase “p”.
Specifications and Implementations
One important thing to point out about .NET is that it is a system of specifications and implementations.
For many components, there is a specification of how it should work, and then one or more
implementation of that specification. The C# compiler is a good example of this. The C# language itself
has a specification about how it should work and what it should compile to. While there is a default, builtin compiler (the Roslyn compiler) that particular component can be replaced with another compiler in a
different stack. As long as the compiler sticks to the specification, it is fine to replace it.
304
Chapter 44
The .NET Platform
The .NET Standard Library is an even better example of this. The .NET Standard Library is actually not a
shared, single implementation, but a specification. Each stack has a different implementation of this
specification. The specification for this library is titled the .NET Standard. The .NET Standard defines what
things should be in the shared class library available to all platforms, and each stack can have a different
implementation that conforms to this standard. (Each stack additionally adds their own unique pieces to
the library as well.)
The Common Language Runtime also has a specification, and each stack has its own implementation.
These specifications allow different stacks or variations of stacks to be able to swap pieces in and out for
different implementations to get the behavior and structure they need. They can be tailored to fit their
specific needs.
A Brief History of the .NET Platform
While the previous section focused on how the .NET Platform is currently organized, a different facet of
the .NET Platform is its history, which is valuable because it sheds light on some of the reasons it is
organized the way it is and also some of the confusing names that are floating around.










Early 2002: The first version of .NET is released, which only included the .NET Framework stack. It
includes the Base Class Library as its standard library. C# and .NET are mostly still what they were
when first introduced.
Late 2002: A second stack, the .NET Compact Framework, is released. This targets platforms such
as phones that have storage, memory, and CPU constraints.
2004: The Mono open source project is started as an alternate implementation of the .NET
Framework intended to run on more platforms than just Windows. This is spearheaded by a
company called Xamarin. This is the beginning of the Xamarin stack.
2004 - 2014: The stacks continue evolving. New content is added to their respective class libraries,
which begin to diverge from each other. New app models such as WPF (2006) continue to be
added.
2012: Portable Class Libraries are introduced in an attempt to make it easier to write code that
works on different stacks.
2014: Microsoft begins to work on a way to make ASP.NET run on Linux machines and Nano
Server. This becomes .NET Core.
February 2016: Xamarin is purchased by Microsoft, and becomes a subsidiary of Microsoft.
June 2016: .NET Core 1.0 is released.
September 2016: .NET Standard is introduced, replacing Portable Class Libraries as a solution to
writing code that works on multiple stacks and in multiple app models. This defines how the
middle layer of the .NET architecture diagram should function, giving motivation for the different
stacks’ class libraries to become more similar to each other instead of more different.
November 2016: .Net Core 1.1 is released.
That brings us to the present—or at least to the time of publishing this book. The .NET Platform as a
whole has grown and changed greatly over time, and the rate of change seems to be accelerating, not
slowing down. More changes are certainly coming in the future.
Binary, Assembly, and Compilers
Now that we’ve outlined the high-level design of .NET and gone through a bit of its history, it’s time to go
back and revisit the components in the .NET Platform in greater depth. We’ll start at the bottom and work
our way upwards.
Binary, Assembly, and Compilers
305
To do that, we should probably start with some conceptual topics first: binary languages, assembly
languages, and compilers.
Computers can only understand binary: 1’s and 0’s. The instructions they follow are all coded in binary
sequences, and the data the read and write is all stored in binary representations. For example, here is a
sequence of three binary instructions:
00100100 00001000 00000000 00000010
00100100 00001001 00000000 00000010
00000001 00001001 01010000 00100000
Can you tell what that does? Turns out, it adds 2 and 2, and stores the result. While it is easy for a
computer to understand this, humans can’t easily make sense of it. But there was a time when humans
controlled computers by manipulating individual bits like this. Of course, that era didn’t last long; it’s just
too error prone and difficult to juggle in our brains.
So humans started building layers on top of binary. The first step to this is a low-level language called
assembly language, or assembler, which can be thought of as a human-readable form of binary:
li $t0, 2
li $t1, 2
add $t2, $t0, $t1
Immediately, you’ll see that this is far more readable than the earlier binary. For example, we can now
actually see the 2’s in there, and the add instruction on the last line. It’s far clearer what’s happening, and
typos or logical mistakes are much easier to see.
Assembly code directly mirrors binary. Each line of assembly is turned into a single instruction in binary
code (with a small handful of exceptions). You can think of assembly as a human-readable form of binary.
Of course, the computer isn’t going to be able to directly run assembly code. We need a way to translate
assembly into binary. This is what a compiler is: a program that translates from a higher-level language to
a lower-level language. In this case, we simply need to create an assembly-to-binary compiler, and we
never have to write in binary again!
But we’re already running into a problem. Not every CPU is made the same. They don’t all use the same
set of 1’s and 0’s to represent the same instruction, and not all CPUs even have the same set of
instructions available. This starts to become a problem for our compiler. This problem leads to the rise of
multiple flavors of assembly, and multiple compilers for the different types of systems we might want to
run on. The plot thickens!
Of course, there’s no need to stop at assembly. It’s more readable than binary, but it’s still not that
readable or concise. Simply doing 2 + 2 took three lines and 25 characters. There’s obviously room for
improvement. So we started making higher-level programming languages like FORTRAN and C++ where
single statements could be translated into many lines of assembly or binary code. Each of these
languages need their own compiler to translate from their own unique syntax to assembly or binary.
Different lines of thought, different use cases, and different users (the programmers) led to an explosion
in the number of programming languages available, some general purpose (Java, C#, Python), some
tailored to a specific task (R, MatLab), and others just plain weird (Ook).
Higher-level languages make life simpler for programmers because less code does more work. But we still
have our original problem that every computer we want to run on is unique, with different instructions
and representations of those instructions. The compilers can handle this, but it means a different
executable .EXE (or equivalent) file for different target machines.
306
Chapter 44
The .NET Platform
This organizational pattern is the traditional compilation model: Source code written in a particular
language is turned to binary code by a compiler. The compiler must know and understand a lot about any
targeted hardware platform in order to make instructions for that particular machine. This leads to either
a large number of compilers, or to a large number of configurations for single, complicated compilers.
The traditional compilation model has its problems, but it is a good model that has served many
programming languages and many programmers well for decades.
Virtual Machines and the Common Language Runtime
More recently, a variation on traditional compilation has arisen. This alternate approach doesn’t strictly
replace traditional compilation; they both have their place. But this second approach has seen a surge in
popularity, and is the approach C# and the .NET Platform takes.
This alternative approach is driven by two fundamental problems of traditional compilation. The first of
these is the sheer number of compilers that need to exist to target each of the different hardware
platforms and their nuances. The second is that compiler programmers need to be not just experts in
their language, but also in every hardware platform they want their language to work on.
A solution to this problem is that of a virtual machine. The term “virtual machine” has a couple of
meanings, depending on the context. In this context, we’re referring to a process virtual machine, which is a
software program that runs another software program (your program) in a way that is independent of the
hardware itself. (This is in contrast to an application virtual machine, which is a software application that
simulates an entire computer, including hardware, operating system, and multiple running applications.)
The .NET Platform’s virtual machine is called the Common Language Runtime, or CLR. Like with many things
in .NET, there is a specification for the CLR, and multiple implementations of it. Each different stack tends
to have its own implementation. The CLR implementation for the main .NET Framework stack is simply
called the CLR. The .NET Core’s implementation is called CoreCLR, while Xamarin uses the Mono Runtime.
A real hardware machine can perform specific instructions, and has binary (and assembly) code that
corresponds to it. similarly, a virtual machine like the CLR has its own instructions that it can perform.
If an application wants to target the CLR virtual machine, rather than compiling to instructions for any
physical machine, it needs to compile to the instruction set that is supported by the CLR. The CLR then
has the responsibility of turning those “virtual” instructions into the “real” instructions for the physical
machine that it is running on.
Effectively, the compilation is split into two steps. The first step is to transform source code into this
virtual instruction set. This is done by the language compiler. Then, as the program is running, the virtual
machine (the CLR) has the responsibility of doing a final compilation step to transform the virtual
instructions into ones that the underlying physical machine can execute. The CLR has its own compiler to
do just this. This compiler is called the Just-In-Time compiler or the JIT compiler. Under normal scenarios,
the JIT compiler translates methods (Chapter 15) one at a time, the first time they are called. This means
nothing gets translated more than once and methods that are never used don’t get JIT compiled.
This compilation pattern solves our two earlier problems. The compilers themselves are only concerned
with one thing. The “main” compiler just has to translate from C# or another language to the virtual
instruction set, and the JIT compiler doesn’t need to care about the specifics of any language but the
virtual instruction set and translating it to the hardware instructions. This also means that compiler
programmers no longer need to be experts in many physical machine architectures, just in the CLR’s
instruction set.
Virtual Machines and the Common Language Runtime
307
Common Intermediate Language
The virtual instruction set that a virtual machine has is referred to as an intermediate language. It can be
represented in a binary format as well as in a text-based format that is essentially the assembly language
for the virtual machine.
For the .NET Platform, the intermediate language is called Common Intermediate Language (CIL). In the
past, it was called Microsoft Intermediate Language (MSIL) and you still see that term used regularly.
Most Intermediate languages tend to be higher level than “normal” binary or assembly languages. For
example, CIL instructions contain concepts about object-oriented programming (all of Part 3) such as
casting, creating objects, and type checking. It also contains information about exception handling
(Chapter 30).
The C# compiler only needs to convert C# source code into CIL instructions, disregarding the specifics of
which computer you intend the application to run on. (It’s targeting the virtual machine, after all.) When
the CLR is running your code, the JIT compiler will convert the CIL instructions to their final form as binary
instructions for the actual physical machine.
Advantages of a Virtual Machine
There are certain advantages and disadvantages to using a virtual machine like the CLR. This section and
the next will dig a little into the good and bad.
Separation of Concerns
Probably the single most important benefit of using a two stage compilation process with a virtual
machine in the middle is the idea of separating concerns. This has been touched on repeatedly in this
chapter. By separating the hardware-specific concerns away from the language-specific concerns, you
allow the C# compiler programmers to not have to worry about the details of many different hardware
platforms, and you also allow the JIT compiler to not worry about any specific language.
Cross-Language Libraries
Because the code targets a virtual machine, you open up the door for any language that targets the
virtual machine to be able to reuse the code without any additional work. For example, the entire .NET
Standard Library is accessible by every language in the .NET Platform, including C#, VB.NET, F#,
IronPython, and IronRuby. If you made your own language and compiled to CIL, you could get access to
the Standard Library for free. And you can write a C# library that is works in all of these languages as well.
Memory Management
The CLR also performs memory management. C# and other .NET languages do not require you allocate
memory for your objects, nor do you have to clean it up when you’re done (in most cases). The CLR will
manage what memory is used, and what isn’t, and move things around to keep things organized. Memory
management is like having a personal assistant whose job is to take care of these things for you, freeing
you up to work on the interesting parts of your program.
There are a few times where you go beyond the bounds of the CLR’s managed memory (Chapter 42), and
when you do, you’ll need to take extra care to clean things up correctly yourself.
Security
Because C# code is running on a virtual machine, it has a high level of control over what code can access
the hard drive, the network, and other hardware. And because it is running inside a virtual machine, a
program can’t gain access to the memory of other programs. This prevents code from doing a whole slew
of dangerous, virus-like activities.
308
Chapter 44
The .NET Platform
The Drawbacks of Virtual Machines
Virtual machines are great inventions, but they don’t come without a few strings attached. In many cases,
the advantages outweigh the problems, but it is still important to understand these tradeoffs when it
comes time to pick a language to use.
Performance
The primary drawback of a virtual machine is performance. Code running on a virtual machine is
generally considered to be somewhat slower than code running without one.
But this isn’t as bad as it seems.
For starters, in many applications, speed isn’t an issue. Think about a GUI application, like a word
processor or a web browser. As much as these applications do, the computer spends most of its time
waiting for the user to press a key or click a button. The holdup is the user, not the computer. In these
cases, a virtual machine won’t hurt anything.
Additionally, it is technically possible for code running on a virtual machine to be faster. This may seem
strange at first, knowing the overhead that the virtual machine needs, but it is possible. Without a virtual
machine, the compiler makes the final decision about what machine instructions are actually going to be
used. That happens on the developer’s machine. With a virtual machine, the final compilation step
happens at run-time, on the computer that will actually run it. The JIT compiler has more information to
work with, so it has the potential to make better decisions about what to actually use. Despite this, in
practice, .NET code does tend to run just a bit slower than its unmanaged counterpart.
Additionally, C# has the capacity to invoke unmanaged non-CLR code if needed (Chapter 42). This allows
you to use C# code for the bulk of your application, and then call your own C or C++ code for the
performance-critical pieces if needed.
Bad for Low-Level Coding
Obviously, there are just some things that can’t be done in a virtual machine, because it has to run as an
application on another computer. So you obviously aren’t going to be writing an operating system using
C#. Nor would you write something like a device driver to talk to actual hardware in C#. These things are
just too low level. For things like this, you must use a different programming language, such as C or C++.
Your Code is More Visible
I occasionally hear people expressing concern about how compiled C# code is easier to decompile and
reverse engineer than other languages. It’s true that CIL code is higher level than raw binary or assembly.
Because it is higher level, it is slightly easier to determine what the code is doing and reverse engineer it.
But CIL is only slightly more readable than non-CIL assembly code. Plus, this can largely be solved by using
a code obfuscator on your compiled projects before shipping them to customers.
The .NET Standard Library
We’ve now thoroughly dissected the bottom layer of the .NET Platform architecture, and we’re going to
move up to the next level: the .NET Standard Library. The .NET Standard Library is a vast collection of
reusable code that can be utilized in any .NET application. While it is considered a “class library”, that term
is a general term; the .NET Standard Library contains not just classes, but structs, interfaces, delegates,
enumerations, etc. that provide things that any type of application might want to leverage. This includes
things like the primitive types (int, string, double, etc.), collections (lists, dictionaries, stacks, queues),
networking, file system access, multi-threading, and much, much more.
The .NET Framework
309
While this book is more about C# as a language, and not the .NET Standard Library, many of the most
important and useful types in the .NET Standard Library are covered throughout the book.
The .NET Standard Library is actually not just a single implementation. Like many things, it has a
specification called the .NET Standard, and each stack has its own implementation of this standard. The
.NET Standard defines the Application Programming Interface (API) of the .NET Standard Library. This API
specifies the types that need to exist and the members (methods, properties, events, etc.) that each type
should have, as well as the expected behavior of those types and those members.
The .NET Standard is actually not a single standard, but consists of multiple tiers, specified as version
numbers. Each version contains everything that lower version numbers contain, and then some.
For example, .NET Standard 1.0 includes the Action and Func types. .NET Standard 1.1 also contains
these because the version number is higher. .NET Standard 1.1 contains additional types not included
with .NET Standard 1.0. For example, it includes ZipArchive. If something only supports .NET Standard
1.0, then it won’t include this.
The higher the version number, the more types and members it includes, but the harder it is to
implement. Different individual stacks or platforms within the .NET ecosystem will support different levels
of the .NET Standard. They’re all striving to have as high of a version number as they can, but there’s a lot
to implement. New platforms in particular will take time to come online.
As of the time of publishing this book, there are 7 levels of the .NET Standard, comprising version 1.0, 1.1,
1.2, etc., up to 1.6. Version 2.0 is nearing completion. More versions in the future will inevitably come as
well.
What this means for you, as a C# programmer, is that you can use more or less of the .NET Standard by
choosing a .NET Standard version. The lower the number, the more broadly distributable your code will
be. The higher the number, the more things you’ll have available to you, but the narrower your
distribution will be. Chapter 46 will discuss how to make your own libraries target the different versions of
the .NET Standard Library.
The .NET Framework
We now move up to the top layer in the .NET Platform architecture diagram presented at the beginning of
this chapter and discuss the different stacks and app models in more detail.
Our starting point is the stack that is the .NET Framework: the oldest, biggest, and most widely used stack
of them all.
The .NET Framework itself includes quite a bit of additional code for you to leverage that isn’t included in
the .NET Standard. (.NET Standard 2.0 should cover much of this though, and as other stacks implement
.NET Standard 2.0, they’ll be mostly caught up.) This additional code is only available if you limit yourself
to targeting just the .NET Framework. For example, if you’re targeting .NET Core or Xamarin, you won’t be
able to use this additional code. (That goes in reverse for .NET Core or Xamarin specific things.)
The Base Class Library
Long before the .NET Standard was defined, the .NET Framework existed, and had the Base Class Library
(BCL). You can think of this as the .NET Standard Library before there was a need for there to be any
standardization at all. (Remember that the .NET Standard Library is a recent development, driven by the
need for cross-platform libraries due to the rise of additional stacks like .NET Core and Xamarin.)
310
Chapter 44
The .NET Platform
There have been many versions of the .NET Framework and its Base Class Library over the years. As of the
time of publishing this book, the highest version number available is 4.6.2, released in late 2016. You will
continue to see new versions of the .NET Framework come out in the future.
The Base Class Library is the .NET Framework stack’s implementation of the .NET Standard, but because it
actually pre-dates the .NET Standard (by well over a decade) it has a weird relationship to it. The .NET
Standard is actually trying to catch up to the things currently included in the Base Class Library. This is in
stark contrast to the other implementations of the .NET Standard Library, which are working to catch up
to the .NET Standard.
The .NET Framework version 4.6.2 implements the .NET Standard 1.6. But it also contains a whole lot of
additional reusable code as well. (Much more in line with the upcoming .NET Standard 2.0.) Earlier
versions of the .NET Framework cover lower versions of the .NET Standard. For example, version 4.6.1
implements the .NET Standard 1.5 (along with a pile of other unique things). Version 4.6.0 implements
.NET Standard 1.4 (plus unique things).
The fact that the .NET Framework’s BCL contains things above and beyond the .NET Standard means you
can also make your code target specific versions of the .NET Framework. This allows you to take
advantage of the extra content in the BCL, but at the cost of losing portability to other stacks.
The Framework Class Library
Closely related to the Base Class Library, the Framework Class Library (FCL) is the total set of all reusable
code that ships as a part of the .NET Framework. It includes the entirety of the Base Class Library
(including everything in the .NET Standard Library and more) as well as the libraries for the various app
models that exist in the .NET Framework stack, such as WPF and ASP.NET.
In some contexts, people use this term interchangeably with the Base Class Library to generally refer to
reusable .NET Framework code, though the two names are technically different.
.NET Core
The .NET Core is the second stack that we’ll look at here. This is a comparatively recent stack that is aimed
at reaching macOS and Linux. Development on .NET Core was started in 2014, with version 1.0 coming
out in June 2016, and 1.1 being released in November 2016. .NET Core is in heavy active development, so
you should expect more updates to this.
The .NET Core stack supports the .NET Standard Library, and meets .NET Standard 1.6 at the time of
publishing this book. Future versions should support .NET Standard 2.0 when it becomes available.
Contrasted with the .NET Framework stack, .NET Core doesn’t have nearly as many unique APIs beyond
the .NET Standard Library. (And beyond the app models it supports.)
Xamarin
The final stack that we’ll discuss is the Xamarin stack, which targets mobile devices like iOS and Android.
This is a stack is built on the open-source Mono project, which is an open-source implementation of the
.NET Framework. The Xamarin stack is designed primarily for mobile devices, especially for iOS and
Android. This stack, as well as Mono itself, has largely been implemented by the company Xamarin, which
was bought out by Microsoft in February of 2016, and is now a subsidiary of Microsoft.
This change has made Xamarin now a part of Visual Studio, which means that Xamarin development is
now just a part of your Visual Studio license. If you’re using Visual Studio Community, you get it for free. If
you’ve paid for Visual Studio Professional or Visual Studio Enterprise, it is included there as well.
App Models
311
The Xamarin platform supports .NET Standard 1.5, and will support .NET Standard 2.0 in the future.
App Models
The .NET Platform supports a wide variety of app models. An app model is an additional library for
creating a specific type of application. In addition to additional reusable code, app models tend to also
provide additional infrastructure, such as security and deployment models as well.
While the .NET Standard Library contains code that is useful for all types of applications, the code
contained in a specific app model is usually only relevant for a single specific type of application. Stated
differently, while the .NET Standard Library is worth learning no matter what kind of work you’re doing in
C#, there’s little value in learning the details of any specific app model besides the ones you are actually
putting to use.
This book doesn’t cover any of the app models in any great depth with the exception of console apps,
which probably doesn’t truly count as an app model. There are two reasons for this. First is just the sheer
scope of the different app models. You could produce long tomes for each individual app model. (You
don’t have to look far to find books about WPF that are 1500+ pages long, and that assume you already
know how to program in C#.) This book just simply can’t do justice to any of the app models, much less all
of the app models.
The second reason is that app models are compartmentalized. This book is intended to be useful for all
C# programmers. You will probably eventually go on and learn about at least one app model in depth
later on, but you almost certainly won’t go learn all of the app models. For any specific app model, the
majority of C# programmers will never need to use it.
GUI App Models: WinForms, WPF, and UWP
The .NET ecosystem supports a number of GUI applications (GUI being a “Graphical User Interface” with
buttons, checkboxes, list boxes, etc.). The oldest of these is Windows Forms (WinForms) and was
introduced in the very early days of .NET. Windows Forms is now in maintenance mode, meaning bug
fixes are still being performed on it, but no new features are currently expected for Windows Forms.
Windows Presentation Foundation (WPF) is a newer GUI app model that is somewhat more complicated
than WinForms, but significantly more powerful and flexible. Generally speaking, WPF (or UWP) is
preferred to Windows Forms for new UI development.
The Universal Windows Platform (UWP) is a third GUI app model. It has a lot in common with WPF (most
of the same controls and the same XAML markup language). UWP is perhaps not quite as powerful as
WPF, but is designed to be able to target a wide variety of platforms (hence the “universal” part of its
name) including Windows desktops, laptops, tablets, phones, the Xbox One, Internet of Things devices,
and HoloLens. If you know you only need to target Windows PCs, then WPF might be a better choice for
you. If you know you want to reach these other platforms, then UWP makes more sense.
Windows Forms and WPF are only available in the .NET Framework stack. UWP works in the .NET
Framework as well as with .NET Core.
ASP.NET and ASP.NET Core
If you are making a web application with C#, then ASP.NET is probably the app model to look at. The
ASP.NET app model is a part of the .NET Framework stack, but ASP.NET Core is an alternative
implementation that runs on the .NET Core stack. The two have some small differences, but are currently
becoming more aligned, rather than different.
312
Chapter 44
The .NET Platform
ASP.NET allows you to do web development with C# on the backend, and JavaScript, HTML, and CSS on
the frontend.
iOS and Android App Models
The Xamarin stack supports iOS and Android app models. These allow you to make C# code that runs on
these devices. The Xamarin stack itself contains quite a bit of code beyond the .NET Standard Library that
is Xamarin specific. This is done to reduce the amount of iOS or Android specific stuff in your mobile apps.
But Xamarin’s app models do allow for and support features unique to just Android and just iOS for
making these specific app types.
Visual Studio Components and Project Types
The last item we should talk about here is a brief discussion on how to begin actually making applications
using the different stacks or the different app models. This book doesn’t get into the details of these
different app models. But I do want to point out that in order to utilize them, you will need to get them
installed (re-running the installer and look for the stack or app model in the list of components to install)
and then create a project of the right type to begin working with a particular app model.
Installing every single stack and every single app model will take up a lot of space. If you’ve got the space,
you can install everything. Otherwise, it makes more sense to just install the workflows and components
that you are actually using.
45
45 Getting the Most from
Visual Studio
In a Nutshell






Outlines the windows that are visible in Visual Studio.
You can exclude files that you do not want in a project without deleting them.
You can show line numbers in your source code, which helps in debugging.
Describes how to use IntelliSense.
Outlines some basic refactoring techniques.
Points out some useful and interesting keyboard shortcuts.
Visual Studio is a very sophisticated program. It is impossible to fit everything that is worth knowing about
Visual Studio into a single chapter in a book. Like many other topics that we’ve talked about, you could
write books about this. In this chapter, we’ll cover some of the most important and most useful features
of Visual Studio. There is a lot more to learn beyond what I describe in this and the next few chapters, but
these things will get you started.
Windows
The Visual Studio user interface is essentially composed of a collection of windows or views that allow you
to interact with your code in different ways. Each window has its own specific task, and there are lots of
windows to choose from.
The Code Window
The main code window, where you have been typing in all of your code for all of your projects, is the most
important window. This window has a lot of settings that you can customize, which I’ll outline in the
section below, about the Options Dialog.
314
Chapter 45
Getting the Most from Visual Studio
While this window is pretty straightforward, I want to point out that at the top of the window, just under
the tabs to select which file to view, there are some drop down boxes that you can use to quickly jump to
a specific type or member of a type in the current file. The middle drop down box lets you jump to a
specific type within the file (you’ll usually only have one) and the right one lets you jump to a member of
the type, like a method or instance variable.
Speaking of jumping to a type or member, there are a few keyboard shortcuts that are well worth knowing
here, as you work on code. If you select something and press F12, you’ll automatically jump to where that
thing is defined. If that happens to be in another file, that file will be opened.
Going the other way, if you press Shift + F12, Visual Studio will find all of the places in the code where the
current selection is used. This works for any type, method, variable, or nearly anything else. These two
shortcuts are convenient for quickly navigating through your code.
The Solution Explorer
This shows a high level view of how your solution and projects are
organized. The Code Window and the Solution Explorer are the two
most commonly used windows.
At the top of the Solution Explorer, you will see an item for your
entire solution. A solution can contain multiple projects, but by
default, when you create a new project you will get a solution with
one project, and both will have the same name. You can always add
more projects to a solution if you need.
Under each project, you’ll see a Properties node, which you can use
to modify a variety of properties about your project.
You’ll also see that each project has its own References node, which
you can use to manage the other projects or DLLs that the project
needs access to. (This is described in detail in Chapter 46.)
Your code is typically organized into namespaces, which are usually placed in separate folders under your
project, and each new type that you create is usually in its own .cs file.
The Options Dialog
315
The Properties Window
You can right-click on any item in the Solution Explorer and choose
Properties to view properties of that file, project, solution, etc.
Doing so will open up the Properties window.
The Properties window shows you various properties about what
you have selected. As you select different things, the properties
window will update to reflect the current selection.
This window becomes even more useful as you start to build GUI
applications, because you’ll be able to use a designer to lay out your
user interface, and by selecting buttons, checkboxes, or other UI
controls, you’ll be able to modify various properties and settings that those items have.
The Error List
The Error List shows you the problems that occurred when you last compiled your program, making it
easy to track down the problems and fix them.
You can double click on any item in the list, and the Code Window will open up to the place where the
problem occurred in your code. You can show or hide all errors, warnings, or messages at the top by
clicking on the appropriate button.
Other Windows
Visual Studio has many other windows as well. To see what other views you can access, look for them
under the View menu. Even the windows that are described above can be opened from here.
The Options Dialog
Visual Studio has tons of settings that you can modify. There are far too many to try to cover here, other
than a few of the most popular ones. To get to these settings, on the menu, click on Tools > Options,
which will bring up the Options Dialog. Like many other programs, these settings are organized into
various pages, and the pages are organized by category in the tree on the left. Selecting different items in
the tree will present different options to configure.
Including and Excluding Files
Sometimes you have a file that is not being used in your project. You may want to remove it from your
project so that you don’t keep seeing it in your Solution Explorer. One option is to simply delete the file
(right-click on it and choose Delete) but sometimes, you don’t actually want to delete the file, just stop it
from being included in your solution.
To exclude a file from your project, simply right-click and choose Exclude From Project. When you do
this, the file will disappear from your Solution Explorer, but it is not permanently deleted.
You can also add a file back in to your solution. This is also helpful if you create a file outside of Visual
Studio and save it in your project directory. To do this, right click on your project (not the solution at the
316
Chapter 45
Getting the Most from Visual Studio
very top) and choose Add > Existing Item.... Browse to find the file you want to add and press Add. The
file will now be included in your project.
Showing Line Numbers
Being able to see line numbers is very important. A lot of times, when something goes wrong, the error
message will tell you what file and line number the problem occurred on, but if you have to manually
count every line to get down to line 937, that information would be pretty useless.
Visual Studio has a way to turn on the display of line numbers. To do this, on the menu, choose Tools >
Options. This opens the Options dialog box:
In the panel on the left, click on the node that is under Text Editor > C#. When you click on this, the panel
on the right will show several options. At the bottom under Settings, check the box that says “Line
numbers.” Once you have done this, you will see line numbers on the left side of your code:
In my opinion, it is unfortunate that showing line numbers isn’t the default, but it’s not.
IntelliSense
Visual Studio has an incredibly powerful tool called IntelliSense (also called AutoComplete in other
programs or IDEs) that makes typing what you want much faster, as well as providing you with quick and
easy access to documentation about code that you are using. You have probably seen this by now. It
usually pops up when you start typing stuff, like this:
Basic Refactoring
317
IntelliSense will highlight the item in the list that is most recently used, making it so that you can simply
press <Enter> to choose it. This means that even if you give a variable a long name, you may only need to
type the first few letters and press <Enter>. This feature makes it easy to give things descriptive names
without needing to worry about how hard it will be to type it all in later.
There are several things that cause IntelliSense to pop up automatically. This includes when you first start
typing a word, when you type a “.” (the member access operator) or parentheses. You can get IntelliSense
to go away by pressing <Esc>, and you can bring up IntelliSense whenever you want it by pressing <Ctrl>
+ <Space>.
IntelliSense also shows you the comments that have been provided for any type or its members. This
includes your own comments, so it is very helpful to write XML documentation comments for your code
as we discussed in Chapter 15.
Basic Refactoring
Refactoring is the process of changing the way code is organized without changing how it functions. The
idea with refactoring is to make it so that your code is better organized and cleaner, making it easier to
add new features in the future. There are books written on the best way to refactor code, but it is worth
pointing out that Visual Studio provides a small set of refactoring tools.
If you really want refactoring power, you should consider a Visual Studio add-on called ReSharper
(http://www.jetbrains.com/resharper/). It’s not cheap, but provides a massive amount of refactoring
support among many other useful features.
Visual Studio has a few basic refactoring tools that are worth pointing out though. For starters, if you have
something that you want to rename, rather than manually typing the new name everywhere, simply select
it in the Code Window, right-click, and choose Rename (F2). Also, if you have a block of code that you
want to pull out into its own method, you can select the code, right-click, and choose Quick Actions, then
Extract Method.
Keyboard Shortcuts
Before finishing up here, there are several keyboard shortcuts that are worth pointing out.







F5: Compile and run your program in debug mode.
Ctrl + F5: Compile and run your program in release mode.
Ctrl + Shift + B: Compile your project without attempting to run it.
Ctrl + Space: Bring up IntelliSense.
Ctrl + . : Show Quick Actions.
Ctrl + G: Go to a specific line number.
Ctrl + ]: Go to the matching other curly brace ({ or }).
318
Chapter 45










Getting the Most from Visual Studio
F12: Go to the declaration of a variable, method or class.
Shift + F12: Locates all places where something is referenced, throughout the project.
Ctrl + R then Ctrl + R: Rename an element of code.
Ctrl + R then Ctrl + M: Extract selected code into its own method.
Ctrl + -: Move back to the last place you were at.
Ctrl + Shift + -: Move forward to the place you were at before moving back.
Ctrl + F: Find in the current document.
Ctrl + Shift + F: Find in the entire solution.
Ctrl + H: Find and replace in the current document.
Ctrl + Shift + H: Find and replace in the entire solution.
46
46 Dependencies and
Multiple Projects
In a Nutshell




A project can reference other projects that you have made, DLLs, or other parts of the .NET
Platform. This gives you access to large piles of previously created code.
You can add DLL references directly to a project.
You can inter-link multiple projects within a single solution together.
NuGet is a powerful and convenient way to reference 3 rd party reusable packages of code.
Most of the projects that we’ve done in this book have been relatively small. All of the code can live
together in a single place, and you don’t intend on reusing any of it elsewhere. Furthermore, we haven’t
really done many things that require using code beyond your own, or the things that just come as a part
of the .NET Standard Library. But that’s not going to last forever.
Your source code is placed into various projects, which show up in the Solution Explorer in Visual Studio.
Once compiled, all source code in a single project is placed together into a single compiled file called an
assembly or sometimes called a package. These assemblies will either take the form of an EXE file (if the
code has a defined entry/start point) or a DLL file (if it has no entry point, and is just meant for reuse).
Most of our code in this book will compile to an EXE file, but it is possible to organize reusable pieces into
separate projects that compile to a DLL instead.
Perhaps more importantly, other people have already packaged up chunks of reusable code for you to
reuse. You can use these libraries in your own projects so that you don’t have to redo their work yourself.
When you write code that utilizes some other library, your code is said to “depend on” the other code. Or
you could say that the other code is a dependency of your code.
This chapter will focus on how you can add dependencies to your own projects, including stuff that you’ve
created in the past, as well as things others have created. We’ll first talk about how to set up your project
to reference other existing code. This comes in several different flavors:
320
Chapter 46
1.
2.
Dependencies and Multiple Projects
Adding a reference to a plain DLL, including one of the .NET Platform DLLs.
Adding a reference to a NuGet package, which is the preferred way to package code for reuse,
and the preferred way to reference this existing code (rather than directly referencing a DLL that
you downloaded from the Internet or something).
You can also split a solution into multiple projects. This allows you group related code together into highlevel sections. When you do this, you can reuse chunks of your code in other programs you make. This
chapter will discuss how to add separate projects to your solution and then cross-reference them.
Adding DLL References
The starting point for adding any reference is the project’s References node in the Solution Explorer:
Each project in your solution will contain a References node. You can expand this to see what your
project is currently referencing. (There are a number of things that are referenced by default for each
different project template.)
This node is the starting point for adding any type of reference to your project. Once something has been
referenced, you will be able to start using it in your code.
To add a reference to a DLL, right-click on this References node and choose Add Reference…. This will
open a dialog window that will allow you to locate the DLL you are interested in adding as a dependency:
On the left side, you can pick from a number of categories of ways to add a reference. For example, if
there is a library that you want to add that is a part of the .NET Platform itself, you can click on
NuGet Packages
321
Assemblies > Framework. This will display a list of .NET Platform components, which you can then check
to add as a dependency for your project.
If you want to add a reference to a DLL that you have on your file system, you can hit the Browse button
at the bottom of the dialog and hunt it down. Once you’ve chosen the dependency, you can click the OK
button to close the dialog. If it was added correctly, the dependency will now show up under the
References node in the Solution Explorer as a new item.
NuGet Packages
While the last section described how to add a reference to any DLL on your file system, this is typically not
the preferred way to handle this in the .NET world. The better approach is to use NuGet packages instead.
The Dependency Management Problem
Copying around individual DLLs can create a lot of headaches for you. This is especially so when the DLLs
you want to use have their own dependencies that they rely on. Your one single dependency might need
17 other DLLs in order to function. Or perhaps it only needs one, but that one has a dependency on one
other thing, which has a dependency on one other thing, which has a dependency on one other thing. The
dependency tree can be very wide, very deep, or both.
It gets worse when versions are added to the mix. When something is dependent on version 1.7.3 of
another package, but you can only find 1.8.0.
This particular problem is lovingly called “dependency hell” or “DLL hell” by those who have faced it.
The NuGet Package Manager
To help solve this problem, many systems and frameworks utilize a tool called a package manager. A
package manager is designed to work through these types of issues for you by arranging dependencies
into packages with supporting metadata for it.
The package manager most commonly used by the .NET Platform is called NuGet. (It’s a play on words!
Like the candy confection nougat! Get it?). NuGet itself is a whole collection of tools, but the primary
points of interest are a command line tool and a Visual Studio extension that ships with Visual Studio.
Most NuGet packages contain only a single DLL, though they aren’t limited to that. A NuGet package could
contain multiple DLLs, tools for the NuGet command line itself to run, random additional files that get
added to a project, etc. But in general, most NuGet packages you might use will just be a single DLL.
NuGet has a ton of third party libraries that you can use in your program. If you think somebody else
might have already made some code to do some particular task, it’s always worth a quick search through
the available NuGet packages to see if there isn’t already something that you could reuse without having
to write your own. The search can be done in Visual Studio itself (the next section) or on nuget.org.
Adding Dependencies with NuGet
In this section, I’ll walk you through how to add a NuGet package to your project as a dependency.
Our discussion here could use any NuGet package, but I’ve chosen to walk through this with the
System.ValueTuple package specifically. This package allows you to use the language-level support for
tuples that C# 7 introduced. The details of value tuples are discussed in Chapter 28. Here we will focus
solely on getting a NuGet package added as a dependency to your project.
The Visual Studio extension for NuGet makes it easy to add new NuGet packages to a project. Adding
NuGet packages is done at the project level, not the solution level. (This is true of all dependencies, not
just NuGet.) If multiple projects need the dependency, you will need to add it to each one.
322
Chapter 46
Dependencies and Multiple Projects
1.
2.
In the Solution Explorer, right-click on the project that you want to add the NuGet package for.
Select Manage NuGet Packages from the context menu. This will open up a new window that
will allow you to manage NuGet packages for this solution. (You would also come back here if you
want to update or delete a dependency from a project.)
3.
4.
At the top of this window, you will see tabs for Browse, Installed, and Updates. Select Browse.
Search for the name (or keywords) of the NuGet package you are looking for. For our particular
example, searching for “ValueTuple” should find the result you need.
In some cases, you may want to look for pre-release packages, not just ones that are considered
official or final. If this is the case, check the box for it next to the search box.
As you begin typing, search results will begin to fill in left side of the panel.
Look for the NuGet package that you want to add (System.ValueType in our specific case.)
5.
6.
7.
Creating and Referencing Multiple Projects
323
8.
The panel on the right side will show you details for the selected NuGet package, including the
license it uses (make sure it’s compatible with what you want to do with it), a description, and any
additional dependencies it may have. If it has other dependencies, NuGet will install those as well.
9. When you’re ready to install your dependency into your project, click the Install button towards
the top on the right side panel, and the dependency will be added to your project.
10. It might ask you to choose between the older packages.config and the newer PackageReference
format for storing NuGet packages. Either one works, but the newer PackageReference version is
probably better if your whole team is using Visual Studio 2017.
11. At this point, you can close this entire tab unless you want to add more NuGet packages.
12. You can verify that a package has been added by finding it under the References node for your
project in the Solution Explorer. If you see it there, it has been added and you can start using it.
Creating and Referencing Multiple Projects
In this section, we’ll talk about how to add extra projects to your solution, how to reference one project
from another, and how to specify that a project should utilize a specific version of the .NET Framework or
a specific level of the .NET Standard (Chapter 44).
Before we get into the specifics of how to do this, it’s worth discussing why you would do this. Compiled
code is grouped at the project level. Each project produces a single DLL or EXE file, regardless of how
many source code files, folders, or namespaces there are in it. Separating code out into different projects,
which results in different DLLs, allows you to reuse those pieces separately from the other pieces.
Imagine your company Wayne Enterprises is making a networked game called BatRPG that you want to
run on PCs, the Xbox One, and iPhones. You might have quite a few projects for this game. For example:






WayneEnterprises.BatRPG.Core: Contains the core functionality of the game. The server can run
this, but so could a client that isn’t playing online.
WayneEnterprises.BatRPG.PC: The PC client for your game.
WayneEnterprises.BatRPG.XboxOne: The Xbox One client for your game.
WayneEnterprises.BatRPG.IPhone: The iPhone client for your game.
WayneEnterprises.Physics: Contains the physics of your game, but is reusable in other games.
WayneEnterprises.GUI: A GUI library that you’ve built yourself for your games.
324
Chapter 46
Dependencies and Multiple Projects
Obviously this is just an example, not instructions on exactly how to structure your game, but it illustrates
the point. This example has pulled out certain pieces that are meant for reuse in many games. And it
separates the core of the game out into another project to be reused by many clients and the server.
Adding Additional Projects
Creating another project in your solution is not hard. Go to the Solution Explorer and right-click on the
top-level solution node. Select Add > New Project… from the context menu. This will bring up the New
Project dialog, which is what you also see when you create a brand new project and solution.
You can add any type of project that Visual Studio has. There are no limitations on mixing and matching.
So add whatever you feel you need. But generally speaking, you will frequently be adding a separate class
library to your solution. Class libraries are meant to be reusable, and not the primary executable.
Depending on what components you have installed in Visual Studio, you will have several different Class
Library type projects to choose from. For example, you might have one called “Class Library (.NET
Framework)”, one called “Class Library (.NET Core)”, and one called “Class Library (.NET Standard)”. Recall
from our discussion in Chapter 44 what these mean.
If you choose the .NET Framework class library, then it will have a dependency itself on the .NET
Framework, which makes it not portable to other stacks. The same goes for the .NET Core class library.
This is good and bad; it allows you to utilize code that only that stack has available, but comes at the
expense of not being able to port it over to the other stacks.
On the other hand, if you choose the .NET Standard class library, you will be able to port the code across
any stack, but only be able to utilize stuff in the .NET Standard.
It’s not impossible to convert a class library from one thing to another, but it’s not exactly trivial either. It’s
usually best to get it right in the first place.
After choosing the type of the project you want to add to your solution, give it a name at the bottom and
click OK to add it to the solution. Once you do this, you should see your new project appear in the
Solution Explorer, next to the original project.
Referencing Projects
Once your additional project has been added, you will need to reference it in order to use the things it
contains in another project. Projects in a solution don’t all automatically cross-reference each other. To
make a project reference another, we’ll return to our original approach for adding references to projects.
Right-click on the project that needs a new dependency and choose Add > Reference (or right-click on the
References node and choose Add Reference). In the groups on the left, choose Projects > Solution. This
will bring up a list of the other projects in the solution. You can check the box by any project that you need
to add as a dependency, and then click OK when done.
Once you’ve done this, you will be able to utilize code in the dependency.
Namespaces and Using Directives
When you start putting code in another project, the namespace will likely be different than your main
project. You can change namespaces manually, and you can also change the default namespace for a
particular project, but the reality is, you do usually want a separate namespace for different projects. (And
for other dependencies like NuGet and directly referencing a DLL, you won’t even have the option of
manually changing namespaces.)
As you start working with multiple projects, you will want to add in using directives for the namespaces
that are contained in the library you’re referencing. Chapter 27 covers this in detail.
Creating and Referencing Multiple Projects
325
If you want to change the default namespace for a new project, you can do so on the project’s property
page. This is accessible by right-clicking on the project and choosing Properties. On the Application tab,
there is a box labeled Default namespace which you can change to whatever you want it to be.
Specifying API Levels
When you make a class library, you choose whether it is a .NET Standard class library, a .NET Framework
class library, a .NET Core class library, etc.
But each of these stacks have different version numbers. You have the ability to choose which version or
level you want your project to be.
This is especially important if your project is a .NET Standard class library, because the level directly
determines how broadly distributable the library is. (The lower it is, the more broadly distributable it is.
But the higher it is, the more classes and other types are available to your own code to utilize.)
Whether you are targeting the .NET Standard or one of the specific stacks, you can choose the API level by
going to the project’s properties (right-click on the project and choose Properties) and on the Application
tab, look for the drop down labeled Target framework. For a .NET Standard class library, you will see the
various .NET Standard versions: 1.0, 1.1, etc. For a .NET Framework class library, you will instead see
versions of the .NET Framework (4.5, 4.5.1, 4.5,2, 4.6.2, etc.).
For a class library that is intended to be reused by other people, you will probably want to prefer the
lowest value you can, without giving up useful classes that you want to leverage. This allows more people
to be able to reuse it. (Don’t just jump immediately to the highest level just because it is there.)
47
47 Handling Common
Compiler Errors
In a Nutshell



Outlines common compiler errors, what causes them, and how to fix them.
Compiler errors are shown in the Error List window.
If you don’t know what a compiler error means, take the time to understand what it is telling
you, fix what errors you do understand, and go to the web for help if all else fails.
As you are writing code, you’re bound to write some that just doesn’t compile. When this happens, C# will
point out the problems that came up so you can fix them. There are hundreds of different types of errors,
each with a variety of possible causes, so we clearly can’t cover everything here (or anywhere). But it is
worth taking some time to look at the most common errors. Additionally, we’ll take a look at a few general
guidelines for fixing these compiler errors.
Understanding Compiler Errors
When you compile your code before you run it, the compiler will need to work its way through your code
and try to make sense of it, before it can turn it into IL code. If you made a mistake in your code, the
compiler will notice the error and report it. This is called a compiler error, or a compile-time error.
Fortunately, this type of problem is relatively easy to solve. The compiler can tell you exactly what went
wrong, and can often even point out how you might be able to fix it.
Compiler errors are shown to you in the Error List, which I described in Chapter 45. The Error List can be
opened at any time by choosing View > Error List from the main menu.
Compiler Warnings
Instead of an error, sometimes you’ll get a more minor problem called a warning. But warnings can
sometimes be more dangerous than an error.
Common Compiler Errors
327
Errors mean that the compiler was completely unable to make sense of what you wrote, so it didn’t finish
compiling. When you get a warning, it means is that the compiler noticed something odd, but it still found
a way to compile your source code. Sometimes, warnings are harmless. A lot of times they come up
because you’re only halfway done with a piece of code, and so things are naturally a little out of place.
But in general, a warning is an indication of a genuine problem in your code. But because the compiler
still produces an executable program to run, it makes you think you can sneak by it. But if the compiler is
pointing it out, it is almost always a real problem, and will eventually bite you, just at run-time instead of
compile time.
Because of this problem, it is best to always try to treat compiler warnings as errors, and eliminate them
as soon as you can. Don’t let dozens (or hundreds) of warnings pile up. Fixing warnings up front will save
you a great deal of trouble down the road.
Common Compiler Errors
We’ll now take some time to look through some of the most common compiler errors, see what they
mean, and look at how to fix them. Of course, we won’t be able to cover all errors, so when we’re through,
I’ll give you some basic principles for fixing other errors that we haven’t been able to discuss.
“The name ‘x’ doesn’t exist in the current context”
Sometimes, you’ll get an error that says that a variable name doesn’t exist in a particular context, meaning
it either can’t find the name anywhere, or if it can find it, it’s in a different place, making it unusable in the
spot it is currently at. One common time that this comes up is if you accidentally misspell a variable name.
If so, that’s an easy fix.
If it’s not a spelling problem, then what this usually means is that you forgot to declare your variable.
You’ll see this if you do something like this:
static void Main(string[] args)
{
int b = x + 7;
}
You’ve used the variable x without ever declaring it. This can be fixed by simply declare it first:
static void Main(string[] args)
{
int x = 3;
int b = x + 7;
}
There are times when you may think you’ve already declared a variable. In fact, you can even see the
declaration! This is where that “in the current context” part comes into play. You may have declared it, but
not in the context that you’re using it.
This gets right down to the heart of variable scope, which I described in Chapter 18. If you’re sure the
variable has been declared and you’re still getting this error, you’ll want to make sure that the variable in
question is still in scope at the place that you’re trying to use it. This may mean moving the variable in
question to a bigger scope.
One example in particular that I think is worth looking at is one where you have a variable that is declared
at block scope, but you try to use it after the block (but still within the method) like this:
static void Main(string[] args)
{
for(int index = 0; index < 10; index++)
328
Chapter 47
Handling Common Compiler Errors
{
// ...
}
index = 10; // Can't use this here. It has block scope, and doesn't exist after the loop.
}
Here, the variable index can’t be used beyond the scope of the for loop. If you want to use this variable
outside of the loop, you also need to declare it outside of the loop:
static void Main(string[] args)
{
int index;
for(index = 0; index < 10; index++)
{
// ...
}
index = 10; // You can use it here, now.
}
“) expected”, “} expected”, or “; expected”
It is very common to see errors that say a parenthesis, closing curly brace, or semicolon is expected.
Interestingly, the solution isn’t always to add ), }, or ; at the spot that the compiler is complaining about.
(Sometimes, but not always.) It just means that the compiler was unable to figure out where the end of a
statement or block was.
In fact, the compiler sometimes thinks the error is in a location that is very different from where the
problem actually lies! This is because the compiler only realizes there’s a problem when it eventually runs
into a place where it no longer makes sense to still think you’re in the same block or statement.
It’s kind of like driving down the road and missing your turn, only to discover the error ten minutes later
when you see you’re leaving the city limits. You don’t know when you missed the turn specifically, just that
at some point along the line, you went too far. That’s exactly what the compiler does, and so you
potentially get the error much later than when it actually occurred.
Take this code, for instance:
namespace Example
{
class Program
{
static void Main(string[] args)
{
for(int index = 0; index < 10; index++)
{
// missing curly brace here...
}
}
} // error shows up here...
We’re missing our closing curly brace for the for loop, which is pretty obvious when we’ve formatted the
code this way. But the compiler doesn’t care about whitespace, so it tries to match the next curly brace it
sees with the end of the for loop, the one after that with the end of the Main method, and the last one
for the end of the Program class. But then it reaches the end of the file, and it knows it should have come
across one more curly brace. Adding the right missing curly brace, bracket, parenthesis, or semicolon will
fix this problem, though sometimes you need to study your code a bit to find out where you went wrong.
Common Compiler Errors
329
It is also worth pointing out that sometimes, missing a single parenthesis, semicolon, or curly brace will
cause a whole pile of errors to show up in the Errors List, because the compiler can’t figure out where
things begin and end. Simply fixing the one problem will often fix lots of errors.
Cannot convert type ‘x’ to ‘y’
There’s a category of data type conversion errors that you’re bound to see at some point or another. This
can come in one of several flavors, like these, below:



Cannot implicitly convert type ‘x’ to ‘y’.
Cannot convert type ‘x’ to ‘y’.
Cannot implicitly convert type ‘x’ to ‘y’. An explicit conversion exists (are you missing a cast?)
What this error means is that you are trying to take one type of data and turn it into another, and the
compiler doesn’t know what to do with it. If you fit in the category of that third error, and you are sure you
want to change from one type to another, it is an easy solution. Just put in a cast to the correct type:
int a = 4;
short b = (short)a; // The cast tells the compiler you know what's happening here.
Whenever you’re required to use an explicit cast, it means there’s the potential to lose data, so you really
should be sure of the explicit cast before doing it.
On the other hand, if you’re running into one of the other errors, it means the C# compiler doesn’t know
of any way to get from the type you have to the type you want. If your intention was truly to convert
between types, there are usually easy ways around that. In most cases, you can simply write a method
that will convert from one type to another, passing in one type as a parameter, and returning the
converted result from it.
Other times this means you made an entirely different mistake in your code. If you weren’t intending to
convert from one type to another, then this error means there was something else wrong here. For
example, it may just mean you didn’t finish typing all of the code you needed. For instance, if you have a
Point class, with X and Y properties, casting is probably not what you wanted in this case:
Point p = new Point(4, 3);
int x = p;
// Fails because the compiler has no clue how to convert Point to int.
Instead, you want to just change your code to get the X property of the point:
Point p = new Point(4, 3);
int x = p.X;
So the true error may not be that the compiler can’t convert from one type to another, but that you
accidentally forgot a part of the code that left the compiler thinking you were trying to convert types.
“not all code paths return a value”
If you see an error that says something along the lines of [Namespace].[Type]. MethodName(): not all
code paths return a value, it simply means that it is possible for the program to go through your code in
a way that reaches the end of the method without ever returning anything.
The code below is perhaps overly simple, but it gets to the heart of what’s going on:
public int DoSomething(int a)
{
if (a < 10)
return 0;
}
330
Chapter 47
Handling Common Compiler Errors
The method is supposed to return an int. If the value of a is less than 10, a value is returned (0). But if a is
10 or higher, it skips that return statement and gets to the end of the method without returning
anything.
To solve this problem, you need to analyze your code to find what paths through your code are failing to
return a value, and add it in. One possible way to fix the problem from the code above is this:
public int DoSomething(int a)
{
if (a < 10)
return 0;
return a;
}
“The type or namespace name ‘x’ could not be found”
As soon as you start trying to use someone else’s code or code in other projects that you’ve created, or
even just putting things in different folders or namespaces, you’re going to run into this error.
You’ll see this error with something like this:
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace Example
{
class Program
{
static void Main(string[] args)
{
MissingClass c = new MissingClass(); // Error here
}
}
}
This means is that the compiler is running into a type name (MissingClass, in this case) that it can’t find.
It’s possible that you just misspelled something. Easy fix.
It is also possible that it is spelled correctly, and that the problem is a little deeper. When you get this
error, you’ll see that it also says “(are you missing a using directive or an assembly reference?)”. The
compiler is pointing you at the two most common causes of this problem.
It is either a missing using directive or a missing assembly/DLL reference. To tell the two apart, think
about where the code is that you are trying to refer to. Is it something you wrote? Something in the
project that you’re currently working on? If so, then it is probably just missing a using directive. The details
about what’s going on with missing using directives and how to fix them are covered in Chapter 27, but in
short, all you need to do is figure out what namespace the missing type is in and add a new using
directive at the top of your file:
using
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Text;
System.Threading.Tasks;
BlackHole.CodeEatingGravity; // Add the appropriate namespace here
namespace Example
{
General Tips for Handling Errors
331
class Program
{
static void Main(string[] args)
{
MissingClass c = new MissingClass();
}
}
}
If you don’t think the compiler knows where the code is because it is in a different project, DLL, or library
that you haven’t told it about, you’ll need to add a reference to the missing assembly, which is discussed
in Chapter 46. Even after you’ve added a reference to the project, you’ll also need to add in a using
directive to access the type in most cases.
“No overload for method ‘X’ takes N arguments”
If you see this error, it means that you’re not calling a method with the correct number of arguments.
Keep in mind that a method could be overloaded, meaning that there are multiple methods with the
same name. You’ll want to make sure you get the one you want. Double-check to see what parameters
are needed for the method you are trying to call.
In some cases, you may also see this error if you’ve got parentheses in the wrong spot or if they’re
missing, so if you think you’ve supplied the right number of parameters, double-check your parentheses.
“The best overloaded method match for ‘X’ has some invalid arguments”
Like the previous error, this means you’re not calling a method correctly. Unlike the previous error, this
one means that you’ve got the right number of parameters, but they aren’t the right types. Go back and
check that the types of all of your parameters match.
Sometimes, to fix this problem, all you need to do is add in a cast to the correct type.
General Tips for Handling Errors
We can’t cover all possible compiler errors here. There are just too many of them. Microsoft lists over 800
different compiler errors that could come up! And that doesn’t even begin to look at the many root causes
and possible fixes for those problems.
One of the key parts of making software is knowing how to find and fix your own problems. Think about
it; you’re making software that no one else has ever made before! That’s cool, but that means you’re going
to run into problems that no one else has ran into before. So it is critical that you know how to deal with
any and every error that comes up in your code.
So in this section, I’ll outline a few guiding principles that should keep you going, even when you have no
clue how to fix the problem.
Ensure that You Understand What the Error Says
Look at the error message. Does each word in the error make sense, or is there something there that you
don’t understand? Do you understand each of the phrases in the error? For instance, take the following
error message:
Access modifiers are not allowed on static constructors
When you see this, stop and think: do you know what an access modifier is? (If not, see Chapter 18) Do
you know what a static constructor is? (Also Chapter 18.) Once you know what all of the pieces mean, the
solution is often pretty straightforward as well. This error is saying you need to remove the public
keyword from this:
332
Chapter 47
Handling Common Compiler Errors
public static MyClass()
{
// initialize the class here...
}
Fix the Errors You Do Understand
There are times that a single actual problem causes lots of errors to show up in the list. If you get a large
pile of errors, look through the list and fix one or two that you understand and recompile. Doing so might
get rid of all of the errors.
Compiling happens in parts. If the compiler can’t get through one part because of an error and you fix it, it
is also possible that when the compiler advances to the next part, additional errors are might come up.
Because of this, don’t worry if by fixing one error, extra errors appear to pop up. The error count shown to
you doesn’t necessarily show the actual number of problems in your code.
The Error May be in a Different Spot
Just because the error list takes you to a particular location, doesn’t mean that the error is actually right
there. When you double-click on an error in the Error List, it takes you to the place that it realized there’s a
problem. That does not necessarily mean that the error is actually on that line. Look around for other
things that look out of place if nothing stands out to you.
Use Microsoft’s Documentation
It used to be that documentation for programming languages and their code libraries were really cryptic
and poorly done. But Microsoft has done an excellent job describing everything that they’ve done with C#
and the .NET Platform. They’re an excellent source for figuring out what your error means. Check it out
here: http://msdn.microsoft.com/en-us/library
Use Other Programmers for Help
Programmers are usually willing to help each other when they run into problems. Programmers, by
nature, like finding problems and fixing things. They’re usually willing and interested in helping beginners
(or pros) to learn, grow, and solve the problems they’re running into.
I can’t help but recommend stackoverflow.com as one of the best sites for overcoming software
development problems. It’s extremely well put together, and it is designed specifically for programming.
In fact, in 99% of all cases, you won’t even need to ask a question, because the question has already been
asked. (Just be sure to read the FAQs before asking questions, because they sometimes throw a fit when
you ask a question that isn’t a “good fit.”)
If all else fails, there’s always the good old-fashioned Google search. Whatever problem you’re running
into, someone else out there has probably come across a similar problem before, and they’ve probably
posted about it on the Internet. (Another thing programmers love doing.)
48
48 Debugging Your Code
In a Nutshell






Debugging your code allows you to take a detailed look at how your program is executing.
This gives you an easy way to see how things are going wrong.
Describes the difference between running in debug mode and running in release mode.
Describes how to analyze exceptions that occur while your code is running.
Shows how to modify your code while you the program is running.
Describes how to set breakpoints in your code, which suspend the program when reached.
Outlines how to step through your code to see how things change.
Once you get through any compiler errors like we discussed in the previous chapter, you can run your
program. Even though we’ve gotten rid of all of the compiler errors, sometimes things go horribly wrong
while your program is running. This could be a crash, or just that your program isn’t doing what you
thought it would. These are called runtime errors, and we’ll talk about how to deal with them here.
Visual Studio gives us the ability to dig into our program while it is running to see what’s going on, so that
we can fix it. This feature is called debugging.
Debugging is extremely powerful and useful when things are going wrong. Your program is unique, so no
one else can tell you what’s going wrong specifically, but I can show you how to use the debugging tools
so that you can find the problems yourself.
Launching Your Program in Debug Mode
The first step is to make sure you start your program in debug mode. When the program is compiled and
ran in debug mode (as opposed to “release” mode) it includes a whole lot of extra information in the EXE
file. This extra information allows Visual Studio can keep track of what is currently being executed. This
extra information slows down your program and makes the EXE file significantly larger, but it makes
finding problems in your code much easier. Of course, when you’re done constructing your program and
you know it is bug free, you can build it in release mode and sell it!
Your program has to be compiled in debug mode or you won’t be able to easily debug it.
334
Chapter 48
Debugging Your Code
When we first made a program back in Chapter 3, we discussed how you could start your program by
pushing F5 or Ctrl + F5. If you want to be able to debug your program, it will be important to use F5, or
choose Debug > Start Debugging... from the menu. If you start it in release mode (Ctrl + F5) you won’t be
able to debug.
Viewing Exceptions
When your program is running in debug mode, if an unhandled exception is thrown, rather than dying,
your program will pause and the computer will switch back to Visual Studio for you to take a look at it.
You’ve probably seen this by now, but below is a screenshot that shows what this looks like:
Visual Studio will mark the line that the exception or error occurred at in yellow, and it will bring up the
Exception popup. On this popup, you’ll see the specific name of the exception that occurred
(DivideByZeroException, in the screenshot above) along with the text description of the problem. Ideally,
this text would have a lot of useful details, but unfortunately, it’s often kind of vague.
The dialog also gives you the option to View Details, which will allow you to dig through the actual
Exception instance to see what it contains.
When the Exception dialog comes up, Visual Studio is giving you a chance to take a detailed look at the
problem so that you can figure out what went wrong and fix it. All of the tools that are available in debug
mode are there to assist you in doing this. Sometimes, you’ll be able to edit your code while the program
is still running, fix the problem, and continue on. In other cases though, once you reach this point, the
computer won’t be able to continue on and you’ll need to restart to check your fixes.
Looking at Local Variable Values
When the program is stopped, you’ll have the chance to see what state your program is currently in. One
of the most useful features available is that you can hover over any variable and see its current value.
Editing Your Code While Debugging
335
If the value is a composite type like a class or struct, you’ll be able to dig down and look at all of the
properties and instance variables that it has.
Note that this information is also available in the Locals Window. In many cases, this window will already
be open for you, down towards the bottom, where the Error List is. If you don’t see it anywhere in Visual
Studio, you can open it up by going to Debug > Windows > Locals.
The Call Stack
The call stack is one of the most useful tools that you’ll have to help you figure out what happened. The
call stack tells you what method called the current method. And yes, this is directly related to the stack
that we described in Chapter 16.
The problem is sometimes caused, not by the method that you are inside of, but by the calling method
instead. You can use the Call Stack Window to see what methods have been called to get you to there.
The Call Stack Window is usually open by default when you’re debugging, but it can be opened by
choosing Debug > Windows > Call Stack from the menu. The Call Stack Window looks like this:
The method that you’re currently in is at the top. (DoSomethingElse, in this case.) Each line below that
shows the method that called it. (DoSomethingElse was called by DoSomething, which was called by
Main.) You can click on one of those other lines and Visual Studio will jump to that method, allowing you
to see what state your variables are in over there.
Editing Your Code While Debugging
In some cases, you will have the ability to edit your code while it is running and then continue running the
program, exactly where you left off, with the edited code. I can’t overstate how cool this is. This is like
being able to change the tires of a car as you drive it down the road without even stopping.
But this doesn’t work in all cases. For starters, if the problem occurred outside of the current method (on
the top of the stack trace) you won’t be able to edit that method and continue. You can only continue
from changes made to the top method on the call stack.
Also, if you’re doing a LINQ query when the exception occurs, you won’t be able to edit.
In many other cases, you’ll be able to edit your code while it’s stopped. To edit your code, once the
program has stopped and switched back to Visual Studio, just start editing your code like you normally
would. If, in the process of editing, you introduce an error that prevents it from compiling, you won’t be
able to resume until you fix the compiler errors.
There are some types of edits that you won’t be able to do. You won’t be able to add a new using
directive. And that means that the normal steps you may do to add a using directive just flat out won’t
show up in the menu. You won’t be able to structurally modify a type, so no adding, deleting, renaming
methods, properties, instance variables, or class variables. The way the debugger swaps out code just
doesn’t allow you to swap out structural changes like that, just changes to functionality. If you make a
structural change, the change you made will be underlined in blue, and Visual Studio will tell you it needs
to recompile and restart to allow the changes to take effect.
336
Chapter 48
Debugging Your Code
Breakpoints
Up until now, we’ve only about debugging code after your program runs into a problem. But you’ll of
course want to be able to occasionally analyze running code before there’s a crash.
Whenever you want, you can set a breakpoint on a particular line of code in Visual Studio. A breakpoint is
a way to mark a line of code so that when the program reaches that line during execution, the program
will halt, allowing you to take a look at the program’s current state.
To set a breakpoint, you simply locate the line you want to set the breakpoint at, and on the left side, click
in the gray area, marked in the image on the left below:
When you do this, a red circle will appear in the left margin on the line you have selected, and the text on
that line will also be marked in red. This looks like the image above on the right side.
You can set as many breakpoints as you want in your program. You can also add and remove breakpoints
whenever you want, even if the program is already running. To remove a breakpoint, simply click on the
red circle and it will go away.
Whenever your program is stopped at a breakpoint, you can press the Continue button (located on the
Standard toolbar, taking the place of the Start button while your program is running) which will leave the
breakpoint and continue on until another breakpoint is hit, or until the program ends and closes.
Stepping Through Your Program
While stopping your program in the middle of execution allows you to do everything you could before
(looking at local variables, the call stack, etc.) it can do a whole lot more. After all, unlike before, the
program hasn’t run into a problem. You’ve stopped it before that happened.
To use these options, while your program is running, find the Debug toolbar among the various toolbars
at the top of Visual Studio. The Debug toolbar looks similar to this:
If you don’t see it, go to View > Toolbars > Debug to open it. This toolbar contains a collection of very
useful tools for stepping through your code.
The debug toolbar may look a little different, depending on your settings and the specific version of Visual
Studio that you have, but the items on the toolbar that we’ll discuss should be available in all of them. If
Stepping Through Your Program
337
you feel like you’re missing something useful, this toolbar (and all other toolbars) can be customized by
clicking on the little down arrow at the end of the toolbar and choosing the items you want displayed.
Items on the Debug Toolbar
The first button on the Debug toolbar is the Break All button, represented by a pause icon. While
your program is executing, you can press Break All, which will halt execution of your program
immediately, and you can see where it’s at. The computer runs your program so fast that this isn’t useful
for fine-grained control. (We’ll see a better tool for that in a second.) Instead, this feature is usually more
useful to figure out why your program is taking so long to do something. Usually, it is stuck in some
infinite loop somewhere, and by hitting the pause button, you can track down where the problem is.
The second button is the Stop Debugging button. This terminates your running program. It is
important to remember that as soon as you push this, you’ll lose any debugging information you
were looking at, so be sure you’ve got the information you need before hitting it.
The third button is the Restart button. This closes the current execution and starts over from the
beginning. This is very convenient for the times that you’ve made a change and you want to restart
to see if the changes are working.
There are three other buttons on the Debug toolbar which make it easy to slowly step
through your code. These are the Step Into, Step Over, and Step Out buttons. The first two
allow you to advance through your code one line at a time, giving you the ability to study what your code
is doing. Step Into and Step Over both advance by one line, but there’s a subtle difference between the
two. The difference is most obvious when you are on a line of code that calls a method. If you use Step
Into, the debugger will jump into the method that you’re looking at, and the next line you’ll see will be at
the start of that method. You can then continue stepping through the code there. If you use Step Over,
the debugger will “step over” the method call, running it entirely, and only pausing again when the flow of
execution returns back to the current method.
If you are looking at a line of code that doesn’t call a method, the two are identical.
On the other hand, if you’re in a method and you know you’re ready to just move back up to the method
that called it, you can use the Step Out button. This will jump forward as many lines as it needs to until it
is back up to the calling method.
There are two other tools that you may find handy for debugging as well, though these are buried away in
a context menu. If you know you want to skip ahead to another specific line, you can click on the line you
want to jump to, right-click, and choose Run To Cursor. This causes the program to run without pausing
until it hits the line that you indicated. This is like setting a breakpoint on that line temporarily and hitting
Continue. This makes it so you don’t need to keep pressing the Step Over button repeatedly.
There’s one other tool that is very powerful, but can also be a bit dangerous. Use it wisely. I’ll point out the
pitfalls that come with it in a second. But first, let me show you what the feature is.
At any point when you’re debugging, you can right-click on a line elsewhere in the method that you’re
debugging and choose Set Next Statement. This is a very handy feature. To illustrate my point, take a
look at the simple example code below, which simulates rolling a six-sided die:
public int RollDie(Random random)
{
return random.Next(6);
}
In most cases, this will probably work fine. But if the object passed in for random was null, you’ll get a
null reference exception on that line when it tries to generate the number.
338
Chapter 48
Debugging Your Code
If you’re debugging, the debugger will halt on that line of code and point out the problem to you. As I said
earlier, in some cases, you can make edits to your code and continue. Perhaps you want to fix this by
checking to ensure that random is not null before calling the Next method like this:
public int RollDie(Random random)
{
if(random != null)
return random.Next(6);
throw new ArgumentNullException(nameof(random));
}
But after you’ve made these edits, if you try to continue executing, you’ll still be on that line (now inside of
the if statement). You could just restart the program, or you could use the Set Next Statement tool by
selecting the if(random != null) line, then right-click and choose Set Next Statement, and then press the
Continue button on the Debug toolbar. This will start your program running at the chosen line. Your
program will check for null like you want, and you’ll be able to move forward without needing to restart.
It’s an extremely powerful tool, but there’s a dark side to it as well. It is incredibly easy to misuse it and put
your program in a weird state that it can’t get into naturally. Take the code below, for instance:
static void Main(string[] args)
{
int b = 2;
if (b > 2)
Console.WriteLine("How can this be?");
b *= 2;
}
In the normal course of execution, we’d never be able to get inside of that if block. But, if you run this
code and set a breakpoint at the closing curly brace of the method, then use Set Next Statement to move
back up to the if statement, b will be 4, and the code inside of the if block will get executed.
It is very easy to accidentally get your program into an inconsistent state like this when you use the Set
Next Statement tool, so it is important to keep this in mind as you use the feature. Even if you think you’ve
fixed a problem, you’ll still want to rerun the program from scratch again, just to be sure everything is
working like it should.
Try It Out!
Debugging Quiz. Answer the following questions to check your understanding. When you’re done,
check your answers against the ones below. If you missed something, go back and review the section
that talks about it.
1.
2.
3.
4.
5.
True/False. You can easily debug a program that was compiled in release mode.
True/False. Debug mode creates executables that are larger and slower than release mode.
True/False. If an exception occurs in debug mode, your program will be suspended, allowing
you to debug it, even if you haven’t set a breakpoint.
True/False. In release mode, Visual Studio will stop at breakpoints.
True/False. In some cases, you can edit your source code while execution is paused and
continue with the changes.
Answers: (1) False. (2) True. (3) True. (4) False. (5) True.
49
49 How Your Project Files are
Organized
In a Nutshell

Your solution structure looks like this:
Solution Directory
.sln file: Describes the contents of your solution.
.suo file: User specific settings for a particular solution.
Project Directory (possibly multiple).
.csproj file: Describes the contents of a project.
.csproj.user file: User specific settings for a project.
Code files and directories, matching various namespaces.
Properties folder.
obj directory: a staging area for compiling code.
bin directory: finalized compiled code.
I once had a professor who hated Visual Studio. He explained that the reason why is because it’s making
programmers dumb. Students were turning in their assignments and they had no clue what they were
even submitting, and they didn’t know what to do when things went wrong with their submission.
Visual Studio has a bit of a bad habit of spewing lots of files all over the place. Some of these files are your
.cs files, which contain your code. Others might be images, DLLs, or other resource files that you have.
Those are all OK. But in addition, Visual Studio loves configuration files.
I personally don’t think that this is a good reason to hate Visual Studio. In fact, this is evidence that Visual
Studio is doing its job. (Though I do wish the contents of these files were simpler and perhaps less tied to
the Visual Studio editor itself.) It hides all of the information needed to compile, run, and publish your
program, and it does it while juggling flaming swords and singing a rock ballad about doing the Fandango.
And the fact that people can get away with not knowing how it works means it’s doing its job right.
340
Chapter 49
How Your Project Files are Organized
Still though, I think it is worth digging into a project and seeing how things are organized and what
everything is for. In this chapter, we’ll take a look at the directory structure that Visual Studio creates and
look at what is contained in each of the files we discover there.
Try It Out!
Demystifying Project Files. Follow along with this chapter by opening up any project of yours and
digging around until you understand what every file you discover means.
In Depth
Version Control. Throughout this chapter, I’ll be pointing out parts of your project’s directory
structure that should be placed in version control and parts that should not. While a full explanation
of version control software is well beyond the scope of this book, it is worth a brief introduction.
Version control software is a category of software that serves two primary purposes: the ability to
share source code among team members, and the ability to track changes to that code over time,
keeping a history of your software’s development. SVN, Git, and Mercurial are all popular and free
version control systems that you can use. (I’d recommend Git or Mercurial, as they are both
“distributed” version control systems.)
The general rule for what goes into version control are that user-specific project files don’t belong in
version control (everyone should have their own copy) and anything that can be rebuilt from other
things (like compiled executable files) should be skipped as well. Everything else can go in your
version control system.
Visual Studio’s Projects Directory
Visual Studio will place each solution or project you create in its own directory. These project directories
are all in the same place by default, though you can pick a different location when you create a project.
By default, these projects are all stored in [Documents Directory]/Visual Studio 2017/Projects, where
[Documents Directory] is your ‘My Documents’ or ‘Documents’ directory.
If you’ve been putting all of your projects in the default location, you should be able to open that directory
and see a folder for each of the projects that you’ve created. All projects have the same overall directory
structure, so once you’ve figured one out, the others should make sense as well.
The Solution Directory
Solutions vs. Projects
The top level folder corresponds to a solution. Remember that a solution can be thought of as a collection
of projects that are all related, and used together to accomplish a specific task.
You may remember that in the past, when you’ve been ready to start on something new, that you’ve
chosen File > New Project from the menu. What you get is actually a new project, created inside of a new
solution. In the past, when you’ve created a project, you’ve also creating a new solution to contain it.
Inside of the solution folder, you’ll find three things. You’ll see a .sln file, a folder for every project in your
solution (only one if you’ve only got one project in your solution), and you may also see a .suo file. If you
don’t see the .suo file, it’s probably still there, just hidden. If you don’t see it, don’t worry too much. It’s not
too critical that you can see it. It is just important to know that it is there.
The Project Directory
341
The .sln File
The .sln file is your solution file. This file is very important. It contains information about what projects are
contained in the solution, as well as solution specific settings like build configurations. The .sln file
contains shared information that everyone will want to keep the same, and it should be included in your
version control system if you have one.
The .suo File
The .suo file is the solution user options file. It contains various settings for the project that Visual Studio
uses. Like I said earlier, it may be a hidden file, so you may not see it. This contains things like what files
you had open when you last used the project, so they can get reopened when the project is reopened.
You could delete this file and nothing bad would happen.
Because this file contains user-specific information, this file shouldn’t go into version control. Instead,
everyone should have their own version of the file.
The Project Directories
In your solution directory, you should find that there is one folder for each project in your solution.
Let me clarify something that has confused many people in the past, including myself. By default, you’ll
have one project in your solution. This project is named the same thing as your solution. So if you created
a new project called LikeFacebookButBetter, what you’ll be looking at here is a folder called
LikeFacebookButBetter inside of a folder called LikeFacebookButBetter. It can be confusing at first.
The key to remember is that the top level one is the solution folder, and the lower level one is the project
folder, and they represent two different things.
The Project Directory
Inside of a project directory, you’ll see even more files. For starters, you will see a file ending with .csproj.
You will also likely see another file ending with .csproj.user. You should also see a Properties folder. And
you’ll also probably find a pile of .cs files, or other folders than contain .cs files and other resources. You
may also see a bin and an obj folder.
The .csproj File
This is one of the more important files that Visual Studio creates. This file essentially defines your project.
It identifies what files are included in your project, as well as listing other assemblies, libraries, or other
projects that this project uses. This file does for projects what the .sln file does for solutions.
Again, if you’re using version control, this one belongs in the repository.
The .csproj.user File
This file, like the .suo file, contains user-based settings, but for your project instead of the solution. Like
the .suo file, every user gets their own copy, so it should not go into version control.
The bin and obj Folders
You may or may not see folders called bin and obj. If you don’t see them, open the project and compile it
and these folders will appear. These are both used for building and storing executable versions of your
projects and solutions (EXE and DLL files).
The difference between the obj folder and the bin folder is that when the program is compiling, at first it
will place stuff in the obj directory. The stuff in here is not necessarily complete, and can be thought of as
a staging area for the final executable code, which will get put into the bin folder. If you’re going to hand
342
Chapter 49
How Your Project Files are Organized
off the EXE to anyone, or attempt to run the EXE from here, you should go with the one in the bin folder,
not the one in the obj folder.
If you open up either of these directories, you’ll likely see a folder in there called Debug or Release. Dig
down further and you’ll end up seeing the EXE file (or possibly a DLL file if the project is a class library)
along with a random assortment of other files. You’ll get a Debug or Release folder depending on how
you’ve built your program. If you’ve told it to run in debug mode, you’ll see a debug directory. If you’ve
told it to run in release mode, you’ll get a release directory.
Both the obj and the bin directories should be kept out of the version control repository, if you’re using it.
And if you’re submitting an assignment for a class, or handing off your code to another programmer,
these directories do not need to be included, because they can be rebuilt. (In fact, doing so is a good idea
if you’re giving it to someone else who has the capacity to compile it themselves. It reduces the total size
of what you’re sending and eliminates the executable files, which many email systems block.)
The Properties Folder
The Properties folder contains properties and resources that you’ve added to your project. For a large
project, this can be quite large.
At a minimum, you’ll probably see an AssemblyInfo.cs file in the Properties folder of the project. This file
contains information about your project, including versioning information. This file is also quite
important, and it is a shared file, so you should put it in version control.
You can open the file and see what it contains, but it is worth pointing out that everything in this file can
be edited through Visual Studio by right-clicking on your project in the Solution Explorer and choosing
Properties, and then on the Application tab click on the Assembly Information... button, where you
can specify the information you want.
Source Files and Everything Else
At this point, we have covered everything except your actual code and other folders and files you may
have added directly to your project in Visual Studio. If you look around, you’re bound to find a bunch of
.cs files, which are your C# code. Any directories that we have not discussed are directories that you have
made yourself in Visual Studio, and each folder that you see likely represents a different namespace. This
is the default behavior, but you can change your namespaces to be something else entirely. I wouldn’t
recommend doing that though, as it is very handy to have your namespaces match up with the folder
structure that the files are in.
Part
6
Wrapping Up
In this final part, we’re going to wrap up everything that we’ve been doing throughout this book, in
more ways than one. Part 6 will cover the following:


Give you several larger Try It Out! Problems for you to tackle, to help you be sure you’ve
learned the things you needed to (Chapter 50).
Discuss where you can go next, now that you’ve learned C#, including C#-based websites and
web applications, desktop applications, and even video games (Chapter 51).
50
50 Try It Out!
In a Nutshell



The best way to learn how to program is to do it. No amount of reading will make you learn.
This chapter contains a variety of interesting problems that you can try out to help you learn.
If you don’t find any of these problems interesting, feel free to pick your own.
This includes the following challenge problems to try out:
Message from Julius Caesar: Encryption using the Caesar cipher.
Reverse It!: A simple game that gives you practice with arrays and indexing.
Pig Dice: A simple multiplayer game involving randomness and dice.
Connect Four: Remake the classic game of Connect Four.
Conway’s Game of Life: An exploration of a zero-player game.
The best way to learn to program is by programming. That’s why I’ve included the Try It Out! sections
throughout this book. It’s also the reason why there’s homework.
In this chapter, I’m going to present a collection of tasks that will hopefully be interesting to you, and give
you something to work on that will help you be sure that you’ve learned the things you needed to.
Of course, if you’ve got your own project that you want to work on, you should go for that instead. As
interesting as these projects are, if you’ve got one of your own in mind, that’s a better choice for you to
work on. You’ll get the best results and learn more from something that you’ve personally chosen and are
excited about.
I should point out that these challenges are not necessarily easy. They’re like the final boss of a video
game, where you’ll use all of the tools and tricks that you’ve learned throughout the game. It may take
hours of coding, maybe even spread out over days or weeks, to get the right answer. (Or not.) Many of
these are based on the projects that I did while learning to program that I thought were the most
interesting, fun, or memorable.
Each of these problems can be kept to a very simple minimum, or you can add extra features to make
them more detailed and more interesting. Feel free to keep going with an idea until you get tired of it.
346
Chapter 50
Try It Out!
By the way, like all of the other Try It Out! problems throughout this book, I’m posting answers to each of
these on the book’s website and you can go there to see a complete working solution. See
http://www.starboundsoftware.com/books/c-sharp/try-it-out/
Message from Julius Caesar
Encryption is the process of taking data and turning it into a form that is no longer readable. This keeps
the information protected, so that people who aren’t supposed to read it can’t. Of course, the person who
is supposed to read it needs to be able to decrypt it and recover the message.
When encryption is performed, the algorithm used to encrypt stuff usually uses a special value called a
key to perform the encryption. If a person has the key, they can usually decrypt the message as well.
The Caesar cipher is one of the simplest encryption schemes, and it is possibly something you used to
send coded messages to friends when you were younger. It is an encryption method that supposedly
Julius Caesar occasionally used when he wrote personal letters.
The basic idea is that for every letter in your message, you shift it down the alphabet by a certain number
of letters. The amount you shift is the key for the algorithm. If you are using a key of 3, A would become D,
B would become E, etc. Once you get to the end of the alphabet, it wraps back around, so Z would be C.
For example, with a key of 4, the message below is encrypted to look like this:
Plain text: EXPERIENCE IS THE TEACHER OF ALL THINGS
Encrypted: IBTIVMIRGI MW XLI XIEGLIV SJ EPP XLMRKW
Write a program that will read in a message from a file (or from the user) and encrypt it, writing out the
encrypted message to another file (or back to the console window). Don’t overwrite the original file.
Ideally, your program will ask for the name of a file to read, and a key (a number) to use in the encryption.
Anything besides a letter (punctuation, numbers, etc.) can either be skipped or passed along to the output
without encrypting it.
Also create code that will do the reverse, decrypting a message given a decryption key.
If you want an extra challenge, try this. The Caesar cipher is really easy to crack. In fact, it is so basic that it
provides little real protection in modern usage. Much more sophisticated algorithms are used now to
encrypt data. To prove the point that the Caesar cipher can be cracked, we’re going to try a “brute force”
approach to crack the following code:
UX LNKX MH WKBGD RHNK HOTEMBGX
With the Caesar cipher, there are only 26 possible keys. Try each one, one at a time, until the decrypted
message makes sense. The simple approach for this is to have a human (you) visually inspect the
decrypted message to see if it makes sense. While it is more work, it is possible to have the computer
figure out if the message has been decrypted by using a dictionary file (containing all or the words in the
English language) and checking to see if all or most of the decrypted words are in it. A high percentage
typically indicates a successful decryption.
Reverse It!
In this task, we’re going to make a simple array-based game. We’ll start by making an array that can hold 9
integer values, and we’ll randomly put the numbers 1 through 9 in the array. Make sure you don’t have
any duplicates. Each number should appear exactly once.
The array will be printed out to the user like this:
Pig Dice
347
5 3 8 6 4 1 2 9 7
Allow the user to type in a number 1 through 9. Whatever the user types in, we’re going to reverse that
many numbers, starting at the front. So from the randomized starting position shown above, if the user
typed in the number 4, we’d end up reversing the first four numbers, and the resulting array would be:
6 8 3 5 4 1 2 9 7
The player can keep typing in numbers, and you will keep reversing the indicated part of the array until
the array is in ascending order:
1 2 3 4 5 6 7 8 9
When this happens, the player has won the game, and the game ends. Before closing, show the number
of moves it took the player to win.
Can you figure out an optimal strategy for this game? It is possible to win this game in no more than 16
moves, regardless of the starting arrangement.
Pig Dice
In the game of Pig Dice, players take turns rolling two six-sided dice. On a player’s turn, they can roll the
dice repeatedly until they either choose to hold (stop rolling) or roll a 1. As long as the player keeps rolling
without getting a 1 on either die, the numbers on the dice are added to the player’s turn score. At any
point, a player can hold, which takes their turn score and permanently adds it to their overall score. If they
roll a 1 on either die, they lose all of the points they had collected in their turn score, nothing extra is
added to their overall score, and it becomes the next player’s turn. When a player’s overall score reaches
100, the game ends, and they have won.
To illustrate, let’s say Alice (player #1) rolls a 3 and a 4. That gives her a turn score of 7. Alice chooses to
roll again, getting a 2 and a 6. That gives her an extra 8 points, for a total turn score of 15 points. Alice
then chooses to hold, and her current turn score is added to her overall score. Alice now has 15 points
overall, and it is now Wally’s turn (player #2). Wally rolls a 2 and a 3, giving him a turn score of 5. Wally
chooses to roll again, but this time, gets a 1 and a 5. Since he rolled a 1, he loses his turn score (which was
5) leaving him still with 0 overall points, and it becomes Alice’s turn again.
Our goal is to create a computer version of Pig Dice. On each player’s turn, allow them choose to either
hold or roll. When a player rolls, generate two random numbers between 1 and 6, and program the game
to follow the logic described above. Between rolls, display the current status of the game, showing both
player’s overall score, and the current player’s turn score. When a player gets up to 100, end the game
and show who won. For an extra challenge, make the game playable by three or more players.
Connect Four
The game of Connect Four is a simple and interesting game played on a grid
with six rows and seven columns. Two players take turns taking tokens and
dropping them into one of the seven columns, where it falls down to either the
bottom if the column is currently empty or to the top of the pile if it is not.
The goal of the game is to get four of your tokens in a row, up and down, side
to side, or diagonally, before the other player does. Your task is to make a
game where two alternating players can select a row (numbers 1 through 7) to
place their token on and play the game of Connect Four.
348
Chapter 50
Try It Out!
There are a few pieces to this game that might be challenging. One will be writing the code to take a row
and add a token to the top of it. On a related note, it may also be tricky to ensure that if a row is full, the
game doesn’t crash, but instead, notifies the player that the move they wanted to make was an illegal
move, and give them another chance. Also, after each move, you’ll want to check to see if that move
caused the player to win.
By the way, while many games like this tend to look great with a GUI the game can still easily be made to
look good by printing out the right things to the console window, as shown below:
.
.
.
.
.
.
.
.
.
.
.
X
.
.
.
.
.
.
.
.
.
O
X
X
.
.
.
.
.
O
.
.
.
.
.
.
.
.
.
.
.
.
Conway’s Game of Life
In 1970, British mathematician John Conway invented a “zero player” game called the Game of Life. This is
not the Milton Bradley board game called LIFE, but rather a mathematical game that simulates life.
The game is “played” on a grid, where the game advances in generations, with cells becoming alive or
dying, based on certain conditions.
If a particular cell in the grid is empty (dead) then it can come to life if the following conditions are met:

Exactly three neighbors that are alive.
If a particular cell in the grid is alive, then it dies in the following generation if any of the following are true:


It has more than three neighbors. (Overcrowding.)
It has 0 or 1 neighbors (Loneliness, I guess...)
For example, look at the little grid below:
In the next generation (round) the top cell that is alive will die, because it only has one neighbor. The one
in the middle will stay alive, because it has two neighbors. The one on the bottom will also die, because it
only has one neighbor. Additionally, just off to the left and the right of the three “alive” cells, there is a cell
with three neighbors, both of which will become alive. After one generation, this will turn into the
following:
Interestingly, if you look at this and follow it through to the next generation, you’ll end up back where you
were initially. This happens to create what is called an oscillator in the Game of Life. It will keep repeating
forever.
Conway’s Game of Life
349
These simple rules for determining when cells in the grid come to life or die create very interesting results
that are much more interesting to see than to read in a book.
Because of this, we’re going to take upon ourselves the challenge of creating the Game of Life as a
computer program.
There are several key parts to this. For one, we will need to store the current grid. There are lots of ways
to do this, but one might be a two dimensional array of bools.
We will also need a method for drawing our current grid. Again, it would look nice with a fancy GUI, but
we can get away without needing to go beyond the console. If you’re sticking with the console, your best
bet is to use 40 columns in your grid, and have each cell take up two characters (an “X” or “.” depending on
if it is alive or dead, plus an empty space) because on the console, characters are twice as tall as they are
wide, and there are 80 columns by default. Using 40 columns with two characters per column gives you
nice, square shaped cells. About 12 rows fit on the console window by default, but you can go up to 20
rows pretty easily and then drag the console window to resize it and make it taller.
Additionally, you will need to create a mechanism to update the grid using the rules described earlier for
cell death and generation. It is important to work with a copy for the next generation or updating one cell
will mess up the calculation in another cell.
Finally, you’ll need to put it all together in a loop that repeats forever, updating the grid, drawing the grid,
and waiting for a while in between, to animate the game and let you see what’s happening. For this, you
might find the Thread.Sleep method useful. We talked about this method in Chapter 39. You will need to
add a using directive to the top of your code for System.Threading to get access to this, as discussed in
Chapter 27. You might also find it useful to be able to clear the console window before you draw the new
grid, using Console.Clear();
The most interesting part about the Game of Life is being able to start with different starting
configurations and watching it evolve. To make this happen, make it so you can load in initial
configurations in a file. The specifics of the file format are up to you, but one possibility is to make them
look like this:
........................................
........................................
........................................
........................................
........................................
........................................
...................X....................
...................X....................
...................X....................
........................................
........................................
........................................
........................................
.......................X................
........................X...............
......................XXX...............
........................................
........................................
........................................
........................................
You can create these files in any text editor, like Notepad.
350
Chapter 50
Try It Out!
When your game is up and running, try loading the following patterns and watch what happens:
Blinker:
Glider:
Pulsar:
Diehard:
There are tons of interesting patterns that show up in the Game of Life, so when you get your game
working, experiment with various configurations, and look online and see what other interesting patterns
exist.
51
51 What’s Next?
In a Nutshell




Your next step might include learning app models like WPF, UWP, ASP.NET, or Xamarin
development, or maybe even game development tools like MonoGame or Unity.
It is also worth your time to learn software engineering, as well as data structures and
algorithms if you haven’t already done so while learning another language.
The best way to learn C# is to program in C#.
You can get more information from MSDN, Stack Overflow, and this book’s website.
As you finish learning everything this book has to offer, you may be wondering where you’re supposed to
go next. And it really depends. I want to take a little time and give you some pointers on where your next
steps might take you.
Other Frameworks and Libraries
If you’ve gone through this book sequentially, you already know all of the basic things you need to know
about the C# language. Much of the rest of your learning in the C# world will be more about playing with
the different .NET stacks and app models, and learning useful libraries for building different types of
programs. Below are some of the most useful ones.
Desktop Application Development
The .NET Platform includes a number of GUI desktop application frameworks, including Windows Forms,
Windows Presentation Foundation (WPF) and the Universal Windows Platform (UWP). We talked about
each of these different app models in depth in Chapter 44. Windows Forms is in maintenance mode, but
is still quite popular. WPF is the most mature and powerful option, but is only supported on Windows
desktops, laptops, and tablets. UWP is the newcomer, and supports a broader set of hardware platforms.
Web Development with ASP.NET
If you’re more interested in making web-based software, ASP.NET is the next step. ASP.NET allows you to
run C# code on the server side, in place of alternatives like PHP, Java (Java Server Pages), Python, or Perl.
ASP.NET is supported in both the .NET Framework stack, as well as the .NET Core stack (ASP.NET Core).
352
Chapter 51
What’s Next?
Mobile Development with Xamarin
The Xamarin stack is built with a focus on mobile app development. If you want to make phone apps with
your C# skills, this is the destination for you. Xamarin is designed to make it easy to make Android and
iPhone apps, and allows you to make code that is largely sharable between the two versions.
Game Development
I’ve saved my personal favorite for last. C# is a very popular choice for game development, and you have
a wide variety of options for doing so in C#. While none of the following are “official” app models from
Microsoft, they all have large communities around to help you.
Unity is a powerful game engine that can target almost any platform you can dream up, and is a popular
choice for C# programmers. Unity isn’t the only full-fledged game engine on the market though. Xenko
and Delta Engine are another two. And the list goes on….
MonoGame is another excellent option. It’s not quite a game engine like Unity is. It doesn’t provide as
much framework for you, but at the same time, it gives you greater control into how you approach things.
You don’t have to do things “the Unity way” but can craft your own way. MonoGame can also target pretty
much every platform out there, and is an open source port of Microsoft’s old XNA framework.
If you want to get really close to the metal, there’s always the option of using SharpDX, a very thin wrapper
around DirectX, or one of a number of C# OpenGL bindings to make your game.
C# has no shortage of game development options.
Other Topics
In addition to learning other frameworks, it is also important to learn things like the best way to structure
your code, or various algorithms and data structures to use. If you are studying C#, and you’re getting
started with formal education at a university, you’ll learn things like this in a software engineering class, or
a data structures and algorithms class. Most universities require you to take these classes, but if it is
optional, don’t skip it. It will be worth it.
If you aren’t at a university, you’ll still want to learn these things. Take the time to learn software
engineering practices. It will be worth the time. You’ll know much better how to organize code into classes
and how to make classes interact effectively. Doing it the right way makes it easier to make changes as
your plans for your program change and grow over time. You’ll learn things like how to best test your code
to make sure it is bug free and working like it should. And you’ll get a basic idea of how to manage your
projects to finish things on time.
Also, be sure to learn about data structures and algorithms for things like sorting and searching. Making
software is a whole lot more than knowing how to write lines of code. And to make truly awesome
software, you’ll need to understand how things will work at a higher level, and how to get the fastest
results, or when to maximize for speed vs. memory usage.
Of course, if you’ve been programming for a while, especially if you had formal education in things like
this, then you probably already know most of this stuff. Generally speaking, the best practices that you
learned in other languages will carry over to the C# world.
Make Some Programs
By far the best thing you can do now is make some programs. Nothing will launch you into the C# world
faster than making a real program. Or several. You’ll quickly see what topics you didn’t fully grasp (and you
can then go back and learn them in depth) or you’ll figure out what extra little pieces you’ll need to learn
still, and you’ll be able to quickly hunt down those things on the Internet.
Where Do I Go to Get Help?
353
Of course, that’s why I’ve included the Try It Out! chapter (Chapter 50), to give you some interesting
problems to work through while testing your knowledge of various aspects of programming.
The programs you choose to work on do not need to be limited to the basics of C# that we’ve discussed in
this book. If you want to try tackling something that also uses WPF, ASP.NET, UWP, or game development,
go for it. There’s no better way to learn to program than by programming.
Where Do I Go to Get Help?
The best part about making software is that you’re creating something that has never been done before.
It is as much a creative process as it is mathematical or analytical. You’re not just manufacturing the exact
same thing over and over again. As such, you’re going to run into problems that have never been
encountered before. You’re bound to run into trouble sooner or later, and so before we part ways, I want
to point out to you a few meaningful things you can do to solve your problems when they come up.
First, obviously, a Google search will go a very long way. That’s always a great place to start.
Second, make sure you fully understand what the code you are working with is doing. If you’re having
trouble with a particular class that someone else created (like the List class, for instance) and a method
isn’t doing what you thought it should, take the time to make sure you understand every piece that you’re
working with. I’ve learned over the years, that if you’re running into a problem with code you don’t quite
understand, you’re not likely to figure the whole problem out until you’ve figured out how to use all of the
individual pieces that are in play. Learn what each parameter in a method does. Learn what exceptions it
might throw. Figure out what all of its properties do. Once you truly understand the things you’re working
on, usually the problem solves itself.
Along those lines, the Microsoft Developer Network (MSDN) has tons of details about every type that the
.NET Standard Library has, with plenty of examples of how to use them. It’s a great resource if you’re
trying to learn how a specific type works. (http://msdn.microsoft.com/library/)
I probably don’t need to say this, but if you’re stuck, and you’ve got team members that might know the
answer, it is always a good idea to talk to them.
If you don’t have team members who can answer the question, Stack Overflow is one of the best sites out
there for programming Q&A. (http://stackoverflow.com/) Stack Overflow covers everything from the basics
up to some very specific, very detailed questions, and you’re likely to get good, intelligent responses from
people. Just be sure to do a Google search to make sure the answer isn’t obvious, and search on the site
as well, to make sure the question hasn’t already been asked. (Because it probably has.)
Parting Words
You’ve learned the basics of C#, and ready to take the world by storm. There’s so much that you can do
with your new knowledge of C#. It truly is an exciting time!
From one programmer to another, I want to wish you the best of luck with the amazing software that you
will create!
52 Glossary
.NET Core
A newer.NET Platform stack that is designed to be more
cross-platform friendly, and primarily targets Linux and
macOS. (Chapter 44.)
version numbers allow you to target more diverse stacks.
(Chapters 1 and 44.)
.NET Standard Library
See .NET Standard.
.NET Framework
The oldest (original) most popular, and most complete
stack within the .NET Platform. Aimed primarily at
Windows computers. This term is frequently used to refer
to the entire .NET ecosystem, though this book makes a
distinction between these two, and calls the entire system
the .NET Platform. (Chapters 1 and 44.)
.NET Platform
The platform C# is built for and utilizes. The term used in
this book to describe the entire .NET ecosystem, including
all stacks (the .NET Framework, .NET Core, Xamarin, etc.),
all app models, the entire .NET Standard Library, the
compilers, CLR runtime, CIL language, and other tools. This
is also sometimes called simply “.NET” and also frequently
called the .NET Framework, though this book makes a
distinction between the two. (Chapters 1 and 44.)
.NET Standard
A specification that defines a vast collection of reusable
types (classes, interfaces, structs, enums, etc.) that exist
across multiple stacks within the .NET Platform. The .NET
Standard allows you to reuse code and produce code that
can be migrated from stack to stack. It allows you to write
code that runs on the original .NET Framework, as well as
.NET Core, Xamarin, and other stacks. The .NET standard
has many different levels or version numbers. Higher
version numbers include more reusable material. Lower
Abstract Class
A class that you cannot create instances of. Instead, you
can only create instances of derived classes. The abstract
class is allowed to define any number of members, both
concrete (implemented) and abstract (unimplemented).
Derived classes must provide an implementation for any
abstract members defined by the abstract base class
before you can create instances of the type. (Chapter 23.)
Abstract Method
A method declaration that does not
implementation or body. Abstract methods
defined in abstract classes. Derived classes
abstract must provide an implementation of
(Chapter 23.)
provide an
can only be
that are not
the method.
Accessibility Level
Types and members are given different levels that they can
be accessed from, ranging from being available to anyone
who has access to the code, down to only being accessible
from within the type they are defined in. More restrictive
accessibility levels make something less vulnerable to
tampering, while less restrictive levels allow more people
to utilize the code to get things done. It is important to
point out that this is a mechanism provided by the C#
language to make programmer’s lives easier, but it is not a
way to prevent hacking, as there are still ways to get access
Glossary
to the code. Types and type members can be given an
access modifier, which specifies what accessibility level it
has. The private accessibility level is the most restrictive,
and means the code can only be used within the type
defining it, protected can be used within the type defining
it and any derived types, internal indicates it can be used
anywhere within the assembly that defines it, and public
indicates it can be used by anyone who has access to the
code. Additionally, the combination of protected internal
can be used to indicate that it can be used within the
defining type, a derived type, or within the same assembly.
(Chapters 18 and 22.)
Accessibility Modifier
See Accessibility Level.
Anonymous Method
A special type of method where no name is ever supplied
for it. Instead, a delegate is used, and the method body is
supplied inline. Because of their nature, anonymous
methods cannot be reused in multiple locations. Lambda
expressions largely supersede anonymous methods and
should usually be used instead. (Chapter 37.)
Anonymous Type
A type (specifically a class) that does not have a formal type
name and is created by using the new keyword with a list
of properties. E.g., new { A = 1, B = 2 }. The properties of
an anonymous type are read-only. (Chapter 19.)
App Model
A component of the .NET Platform that allows you to easily
create a specific type of application. This primarily consists
of a library of reusable code for creating applications of
that type, but also contains additional infrastructure such
as a deployment model or a security model. (Chapter 44.)
Argument
See parameter.
Array
A collection of multiple values of the same type, placed
together in a list-like structure. (Chapter 13.)
ASP.NET
355
contains a starting point for an application, while a DLL
contains reusable code without a specified starting point.
See also project and solution. (Chapter 44.)
Assembly Language
A very low level programming language where each
instruction corresponds directly to an equivalent
instruction in machine or binary code. Assembly languages
can be thought of as a human readable form of binary.
(Chapter 44.)
Assignment
The process of placing a value in a specific variable.
(Chapter 5.)
Associativity
See Operator Associativity.
Asynchronous Programming
The process of taking a potentially long running task and
pulling it out of the main flow of execution, having it run on
a separate thread at its own pace. This relies heavily on
threading. (Chapters 39 and 40.)
Attribute
A feature of C# that allows you to give additional meta
information about a type or member. This information can
be used by the compiler, other tools that analyze or
process the code, or at run-time. You can create custom
attributes by creating a new type derived from the
Attribute class. Attributes are applied to a type or
member by using the name and optional parameters for
the attribute in square brackets immediately above the
type or member’s declaration. (Chapter 43.)
Base Class
In inheritance, a base class is the one that is being derived
from. The members of the base class are included in the
derived type. A base class is also frequently called a
superclass or a parent class. A class can be a base class,
and a derived class simultaneously. See also inheritance,
derived class, and sealed class. (Chapter 22.)
Base Class Library
An app model for building web-based applications using
the .NET Framework or .NET Core stacks. This book does
not cover ASP.NET in depth. (Chapter 44.)
The .NET Standard Library implementation that is a part of
the .NET Framework. It is the most expansive and most
widely use implementation of the Standard Library
(Chapter 44.)
Assembly
BCL
Represents a single block of redistributable code, used for
deployment, security, and versioning. An assembly comes
in two forms: a process assembly, in the form of an EXE file,
and a library assembly, in the form of a DLL file. An EXE file
See Base Class Library.
356
Glossary
Binary Code
C++
The executable instructions that computers work with to
do things. All programs are built out of binary code.
(Chapters 1 and 44.)
A powerful all-purpose programming language that C# is
largely based on. C++ is generally compiled to executable
code, and is not run on a virtual machine. (Chapter 44.)
Binary Instructions
Casting
See Binary Code.
See Typecasting.
Binary Literal
Checked Context
A literal that specifies an integer in binary, and is preceded
by the marker “0b”: 0b00101001. (Chapter 6.)
A section of code wherein mathematical overflow will
throw an exception instead of wrapping around. An
unchecked context is the default. (Chapter 43.)
Binary Operator
An operator that works on two operands or values. Many
of the most common operators are binary, such as
addition and subtraction. (Chapter 7.)
Bit Field
The practice of storing a collection of logical (Boolean)
values together in another type (such as int or byte) where
each bit represents a single logical value. Enumerations
can also be used as a bit field by applying the Flags
attribute. When working with a bit field, the bitwise
operators can be used to modify the individual logical
values contained in the bit field. (Chapter 43.)
Bitwise Operator
CIL
See Common Intermediate Language.
Class
One of several categories of types that exist in the C#
language that can be custom designed. A class defines a
set of related data that belongs to a single type or category
of objects in the real world, or representation of a concept
in the domain model, along with the methods that are used
to interact with or modify that data. A class is as a blueprint
for objects or instances of the class, defining what they
store and what they can do. All classes are reference types.
See also struct, type, and object. (Chapter 17.)
One of several operators that operate on the individual
bits of a value, as opposed to treating the bits as a single
value with semantic meaning. Bitwise operators include
bitwise logical operators, which perform operations like
and, or, not, and xor (exclusive or) on two bits in the same
location of two different values. It also includes bit shift
operators, which slide the bits of a value to the left or right.
In C#, the extra spots are filled with the value 0. (Chapter
43.)
CLR
Boolean
Command Line Arguments
Pertaining to truth values. In programming, a Boolean
value can only take on the value of true or false. Boolean
types are a fundamental part of decision making in
programming. (Chapter 6.)
Built-In Type
One of a handful of types that the C# compiler knows a lot
about, and provides special shortcuts for to make working
with them easier. These types have their own keywords,
such as int, string, or bool. (Chapter 6.)
Breakpoint
While debugging, a line of code may be marked with a
breakpoint, and when the program reaches that point, the
program will pause and switch back to the Visual Studio
debugger, allowing you to take a look at the current state
of the program. (Chapter 48.)
See Common Language Runtime.
Code Window
The main window, usually in the center of the screen,
which allows you to edit code. The window can show
multiple code files tabbed together. Any hidden window
can be opened up through the View menu, or the View >
Other Windows menu. (Chapter 45.)
When a program is started, arguments may be provided
on the command line of a program, which are passed into
the program’s main method where it can use them. These
parameters are command line arguments. (Chapter 43.)
Comment
Additional text placed within source code that is designed
to be read by any humans that are working with the code.
The C# compiler ignores comments entirely. (Chapter 4.)
Common Intermediate Language
A special high-level, object-oriented form of assembly code
that the CLR is able to execute. It includes instructions for
things like type conversion, exceptions, and method calls.
(Chapter 44.)
Glossary
Common Language Runtime
Covariance
A virtual machine that any .NET language including C# is
built to run on. The Common Language Runtime, often
called the CLR, converts CIL code that the C# compiler
created into binary instructions for the computer to run on
the fly. (Chapter 44.)
See Generic Variance.
Compile-Time Constant
357
Critical Section
A block of code that should not be accessed by more than
one thread at once. Critical sections are usually blocked off
with a mutex to prevent multiple simultaneous thread
access. (Chapter 39.)
See Constant.
Compiler
A special program that turns source code into executable
machine code. (Chapters 1 and 44.)
Compound Assignment Operator
An operator that combines a normal math operation with
assignment, as in x += 3; (Chapter 7.)
Conditional Operators
Curly Braces
The symbols { and }, used to mark blocks of code. (Chapter
3.)
Debug
The process of working through your code to find and fix
problems with the code. (Chapter 48.)
Declaration
The operators && (and operator) and || (or operator),
which are used to perform checks for multiple conditions.
(Chapter 10.)
The process of creating something, specifying the
important information about it. This is typically used to
refer to variable declaration, but it is also applied to
methods and type definitions. (Chapter 5.)
Constant
Decrementing
A special type of variable that, once set, cannot be
modified. Constants come in two variations. Compile-time
constants are created when the program is compiled,
using the const keyword. These are treated as global
constants and cannot be changed without recompiling
your program. Run-time constants, created with the
readonly keyword, are variables that cannot be modified
once assigned to. However, they can be assigned while the
program is running. For instance, a type may have readonly instance variables, allowing instances of the type to be
created with different values, but forcing the variables to
be immutable. (Chapter 43.)
Subtracting 1 from a variable. See also Incrementing.
(Chapter 9.)
Constructor
Dependency
A special type of method that initializes an instance of a
type. The role of a constructor is to ensure that the new
instance will be initialized to a valid state. Like a method, a
constructor’s definition may include any number of
parameters. A constructor must have the same name as
the type and no return type. A type may define multiple
constructors. When creating a new instance of a type, a
constructor is called, along with the new keyword. If a type
does not explicitly include a constructor, the C# compiler
will automatically generate a default parameterless
constructor. (Chapters 17 and 18.)
Contravariance
See Generic Variance.
Delegate
A way to treat methods like objects. A delegate definition
identifies a return type and a list of parameter types. A
delegate is created in a way that is similar to declaring a
variable, and can be assigned “values” that are the names
of methods that match the return type and parameter list
of the delegate. The delegate can then be called, which will
execute whatever method is currently assigned to the
delegate and return the result. (Chapter 32.)
A separate library (frequently a DLL) or resource that
another project references and utilizes. The project is said
to “depend on” the referenced library. (Chapter 46.)
Derived Class
In inheritance, the derived class extends or adds on to
another class (the base class). All members of the base
class, including instance variables, methods, and events
also exist in the derived class. Additional new members
may also be added in a derived class. In the case of
multiple levels of inheritance (not to be confused with
multiple inheritance) a class may act as both a base class
and a derived class. A derived class is also sometimes
called a subclass. See also base class and inheritance.
(Chapter 22.)
358
Glossary
Digit Separator
EXE
An underscore character (‘_’) placed between the digits of
a numeric literal, used to organize the digits more cleanly,
without changing the meaning of the number. E.g.,
1_543_220. (Chapter 6.)
A specific type of assembly that contains an entry point
which can be started and run by the .NET Platform. See
also assembly and DLL. (Chapter 44.)
Divide and Conquer
Enumeration
The process of taking a large, complicated task and
breaking it down into more manageable, smaller pieces.
(Chapter 15.)
A specific listing of the possible values something can take.
In C#, enumerations are used to represent a type of data
where there is a known, specific, finite set of options to
choose from. (Chapter 14.)
Division by Zero
Event
An attempt to use a value of 0 on the bottom of a division
operation. From a math standpoint, it is meaningless and
isn’t allowed. In programming, it often results in your
program crashing. (Chapter 9.)
A delegate-based mechanism that allow one part of the
code to notify others that something specific has occurred.
Event handlers are methods which match a particular
delegate and they can be attached or removed from the
event, allowing it to start or stop receiving notifications
from the event. (Chapter 33.)
DLL
A specific type of assembly that contains no specific entry
point but rather a collection of code that can be reused by
other applications. See also assembly and EXE. (Chapter 46.)
Dynamic Object
An object whose members (methods, properties,
operators, etc.) are either changeable at run-time or whose
members are not known until run-time. A dynamic object
should be stored in a variable of type dynamic, so that
dynamic type checking can be done. (Chapter 41.)
Dynamic Type Checking
The act of checking that members such as methods,
properties, operators, etc. that are invoked on an object
exist at run-time, instead of earlier at compile time. This is
necessary for dynamic objects, which can change their
members at run-time or whose members are not known at
run-time. This is contrasted with static type checking,
which happens at compile time. Most C# objects are
checked dynamically type checked, with the exception of
variables of type dynamic. (Chapter 41.)
E Notation
A way of expressing very large or very small numbers in a
modified version of scientific notation (e.g., 1.3 x 1031) by
substituting the multiplication and 10 base with the letter
‘E’ (e.g., “1.3e31”). In C#, float, double, and decimal can all
be expressed with E notation. (Chapter 6.)
Exception
An object that encapsulates an error that occurred while
executing code. The object is “thrown” or passed up the call
stack to the calling method until it is either handled
(“caught”) or is thrown from the Main method, causing the
program to crash. (Chapter 30.)
Explicit
A term frequently used to mean something must be
formally stated or written out. The opposite of “implicit.”
(Chapter 9.)
Extension Method
A special type of static method that appears to be a part of
a class but is actually defined outside of it. This allows you
to create methods for types that are sealed or that you do
not have access to the source code for. (Chapter 36.)
FCL
See Framework Class Library.
Field
See instance variable.
Fixed Size Array
See Enumeration.
An array variable that is always the same size, and whose
bytes are allocated as a part of the struct it belongs to,
instead of elsewhere in the heap. Also called a “fixed size
buffer.” Used primary for interoperating with nonmanaged code. (Chapter 42.)
Error List
Fixed Statement
Enum
A window in Visual Studio that displays a list of compiler
errors and warnings. Any hidden window can be opened
up through the View menu, or the View > Other Windows
menu. (Chapter 45.)
A statement that causes a normal reference to be “pinned”
in place, barring the garbage collector from moving it
temporarily. This is only allowed in an unsafe context, and
allowing it to access managed objects. (Chapter 42.)
Glossary
Floating-Point Type
One of several built-in types that are used for storing realvalued numbers, such as fractional or decimal numbers.
(Chapter 6.)
Framework Class Library
A massive collection of reusable code that is a part of the
.NET Framework. This includes all of the Base Class Library,
plus additional code, including all of the app models in the
.NET Framework stack. (Chapter 44.)
Fully Qualified Name
The full name of a type, including the namespace that it
belongs in. (Chapter 27.)
Function
A chunk of reusable code that does some specific task. A
function is identified by a name, and indicates the inputs
provided to the function (parameters) and the values it
produces as a result (return values). Nearly all functions in
C# are methods (owned by a class) with the exception of
local functions. The terms “function” and “method”
frequently used interchangeably in C# programming.
(Chapter 15.)
Garbage Collection
The process of removing objects on the heap that are no
longer accessible. Garbage collection in C# is automated.
It makes it so that you do not need to worry as much about
memory leaks, and you do not need to manually deallocate
memory for your program, in most cases. See also
managed memory. (Chapter 16.)
Generic Variance
A mechanism for specifying hierarchy-like relationships
among generic types. While one class is derived from
another, Generic<Derived> has no association to
Generic<Base>. Generic variance allows you to set up
rules for how the generic class can be used in hierarchylike relationships, but is dependent on how the generic
type parameter is used inside of the generic class. If a type
is only used as an output from the generic type, it can be
made covariant, which makes Generic<Derived>
substitutable for Generic<Base>. If a type parameter is
only used as input to the generic type, then it can be made
contravariant, which makes Generic<Base> substitutable
for Generic<Derived>. If the generic type is used as both
input and output, it must remain invariant, and
Generic<Base> and Generic<Derived> will have no
substitution relationship. (Chapter 43.)
Generics
A mechanism that allows you to create classes that are
type safe, without having to commit to specific types at
359
compile time. Rather than creating something that can
work with only a specific type (and then creating a very
similar version for other types) and instead of using a very
generalized type (like object) and casting to and from that
to the wanted type, generics can be used to specify that at
run-time, a certain specific type will be used, but it is
currently unknown, and may be different in different
instances. (Chapters 25 and 26.)
Global Namespace
The root level namespace. If your code is not placed in a
namespace block, then it will live in the global namespace.
In some instances where name conflicts occur, you can
reference a name by starting at the global namespace by
starting with global:: followed by the fully qualified name
of a namespace or type. (Chapter 43).
Heap
One of two main parts of a program’s memory. The heap
is unstructured, and any part of the program can access
the information on the heap as needed. Because the heap
is unstructured, memory allocation and deallocation must
be handled carefully. The CLR manages the heap (see
managed memory) using garbage collection. See also stack
and reference type. (Chapter 16.)
Hexadecimal
A base-16 numbering scheme, popular in computing
because of its ability to represent the contents of a byte
using two characters, which uses 16 total digits, instead of
the 10 that is used in a normal decimal system. These
characters are 0 through 9, then A through F. (Chapter 6.)
Hexadecimal Literal
A literal that specifies an integer in hexadecimal, and is
preceded by the marker “0x”: 0xac915d6c. (Chapter 6.)
IDE
See Integrated Development Environment.
IL
See Common Intermediate Language.
Implicit
A term frequently used to mean something happens
without needing to be specifically stated. The opposite of
“explicit.” (Chapter 9.)
Incrementing
Adding 1 to a variable. See also Decrementing. (Chapter 9.)
Indexer
An indexer is a part of a type that defines what the indexing
operator should do for the type. Indexers can use any
360
Glossary
number of parameters, and can use any type—they’re not
just limited to integers. (Chapter 35.)
access operator) is used, but it can also be brought up any
time by using Ctrl + Space. (Chapter 45.)
Inference
Internal
See Type Inference.
See accessibility level.
Inheritance
Invariance
The ability of one class to include all of the members that
another class has, and add on additional members.
Inheritance is designed to mimic an is-a relationship, or an
is-a-special-type-of relationship—in other words, things
where one type is a special type of another. For example,
an octagon is a special type of polygon, and so an Octagon
class may inherit from, or be derived from, a Polygon class.
C# does not allow for inheritance from more than one base
class (multiple inheritance) but a similar effect can be
accomplished by implementing multiple interfaces.
Inheritance can be many levels deep. Every type in C# is
ultimately derived from the object type. Note that structs
do not support inheritance. (Chapter 22.)
See Generic Variance.
Instance
Implicitly Typed Local Variable
See object.
Interface
A listing of specific members that a type that implements
the interface must have. It defines a specific contract that
a type needs to present to the outside world. A type may
implement multiple interfaces. All members defined in any
interface that a type implements must be defined in the
type. (Chapter 24.)
Integer Division
A special kind of division, used when working with only
integral types, in which any remainder or fractional part of
the result is dropped. Using integer division, 7 / 2 is 3.
(Chapter 9.)
Integral Type
One of several built-in types that are used for storing
integers. (Chapter 6.)
Integrated Development Environment
A program that is built for the purpose of making it easy to
create programs. It typically includes a source code editor
and a compiler, along with many other features to assist
with things like project management, testing, and
debugging. (Chapters 1 and 45.)
IntelliSense
A feature of Visual Studio that performs auto-completion
of various tasks such as name completion, but also
provides a convenient way to view the XML documentation
comments of types and their members. IntelliSense is
brought up automatically when the dot operator (member
Immutability
A feature of a type (or sometimes, just a member of a type)
that prevents it from being modified once it has been
created. In order to make changes to an instance, a new
instance must be created with the desired changes.
Immutability provides certain benefits, including simplicity,
automatic thread safety, usable as keys in a dictionary or
hash table, and you do not need to make defensive copies
when returning them from a method or property. Value
types should be made immutable in most cases. Classes
should be immutable when possible. (Chapter 21.)
Using type inference, a local variable’s type does not need
to be specified in some cases. Instead, the var keyword is
used to indicate that type inference should be used. Only
local variables can be implicitly typed. It is important to
remember that implicitly typed local variables actually do
have a specific type (they’re not loosely typed) but that type
is determined by the compiler instead of the programmer.
(Chapter 6.)
Instance Variable
A non-static variable that is declared as a member of a
type. As such, it is available throughout the type, and
depending on its accessibility level, it may be accessible
from outside the type as well. Contrasted with a static class
variable, where all instances of the type share the same
variable, each instance of the type will have its own
variable that functions independently of the variable with
the same name in other instances. It is good practice to
make sure instance variables are not accessible from
outside of the type to adhere to the principle of
encapsulation. (Chapters 17 and 18.)
Iterator
A mechanism for visiting or looking at each item in a
collection of data. A foreach loop can “iterate” over (or
loop over) items in any type that provides an iterator.
(Chapter 43.)
Jagged Array
An array of arrays, where each array within the main array
can be a different length. (Chapter 13.)
Glossary
Java
A high-level, all-purpose programming language similar to
C#. Like C#, it also runs on a virtual machine. (Chapter 1.)
JIT Compiler
See Just-in-Time Compiler.
Just-in-Time Compiler
The process of converting IL code to executable code right
before it is first needed. The CLR uses Just-in-Time
compiling (JIT compiling) as it runs C# code. (Chapter 44.)
Keyword
A special reserved word in a programming language.
(Chapter 3.)
Lambda Expression
361
have scope smaller than the entire method. A local variable
that is declared inside of a loop, if statement, or other
block will only have block scope, and cannot be used
outside of that block. (Chapter 15.)
Loop
To repeat something multiple times. C# has a variety of
loops, including the for, while, do-while, and foreach
loops. (Chapter 12.)
Managed Memory
Rather than requiring you to allocate and deallocate or free
memory on the heap, the CLR keeps track of this for you,
freeing you to concentrate on other things. Memory that is
no longer used is cleaned up by the garbage collector. The
CLR also rearranges memory to optimize space. (Chapter
16.)
An anonymous method that is written with a simplified
syntax to make it easy to create. Lambda expressions are
powerful when used where a delegate is required. If a
lambda expression becomes complicated, it is usually
better to pull it out into a normal method. (Chapter 37.)
Member
Language Integrated Query
A hardware instruction that indicates that pending
memory reads or writes must complete before passing the
memory barrier. Memory barriers are important when
multiple threads access the same data in a way that isn’t
otherwise thread-safe (for example a lock). Access to a
specific piece of data can be protected with a memory
barrier by using the volatile keyword on the variable.
(Chapter 43.)
A part of the C# language that allows you to perform
queries on collections within your program. These queries
involve taking a set of data and then filtering, combining,
transforming, grouping, and ordering it to produce the
desired result set. Often called LINQ (pronounced “link”).
LINQ can be done with a special query syntax or with
simple method calls. (Chapter 38.)
Left Associativity
See Operator Associativity.
LINQ
See Language Integrated Query.
Literal
A direct representation of a value in source code. For
example, in the line int x = 3; the 3 is an integer literal. The
built-in types generally can all be created with a literal,
rather than by use of the new keyword. (Chapter 6.)
Local Function
A function that is contained directly in another function or
method. It is given a name, parameters, and return type,
but is only accessible from within the function that
contains it. (Chapter 28.)
Local Variable
A variable that is created inside of a method, and is only
accessible within that method. Some local variables may
Anything that can belong to a type, including instance
variables, methods, delegates, and events. (Chapter 17.)
Memory Barrier
Method
A type of function that is a member of a type (a class, struct,
etc.) Nearly all functions in C# are methods, with the
exception of nested functions, and the two terms are
largely interchangeable. (Chapter 15.)
Method Body
See method implementation.
Method Call
Going from one method into another. When a method is
called, data may be sent to the method by passing them in
as parameters to the method. When a method call is
finishing, information may be sent back or “returned” from
the method. A method may call itself (recursion). Method
calls are kept track of on the stack. As a new method is
called, a new frame is placed on the stack. As the flow of
execution returns from a method call, the top frame,
representing the current method is removed, returning
execution back to the calling method. (Chapter 15.)
362
Glossary
Method Call Syntax
NaN
Contrasted with query syntax, method call syntax allows
you to perform LINQ queries using normal methods and
lambda expressions, rather than the context-dependent
keywords. (Chapter 38.)
A special value used to represent no value. It stands for
“Not a Number.” (Chapter 9.)
Method Implementation
The actual code that defines what the method should do.
This is enclosed in curly braces right below the method
declaration in most cases. A method without an
implementation is an abstract method, and can only be
made in an abstract class. (Chapter 15.)
Method Signature
The name of the method and types of parameters that a
method has. This does not include its return type or the
names of the parameters. This is largely what distinguishes
one method from another. Two methods in a type cannot
share the same method signature. (Chapter 15.)
Mutex
See Mutual Exclusion.
Mutual Exclusion
Setting up code so that only one thread can access it at a
time. The mechanism that forces this is often called a
mutex. (Chapter 39.)
Named Parameter
When passing values into a method, the name of the
parameter they go with can be explicitly named, allowing
the parameters to be given out of order. (Chapter 28.)
Namespace
Nesting
Placing one statement or block of statements inside of
another block. (Chapter 10.)
NuGet Package Manager
A tool for making it easy to work with many 3rd party
packages and libraries in your code. NuGet is built into
Visual Studio. It is the most common tool for working with
references to other code libraries. (Chapter 46.)
Null Reference
A special reference that refers to no object at all. (Chapter
16.)
Nullable Type
A mechanism in C# for allowing value types to additionally
be assigned null, like a reference type. A nullable type is
still a value type. A nullable type is simply one that uses the
Nullable<T> struct, but the C# language allows for a
simplified syntax, by using the type, followed by a ‘?’. For
example: int? a = null; (Chapter 43.)
Object
Objects are instances of a type (often a class) and
represent a specific instance or occurrence of a defined
type. This is contrasted with the class (or other type
definition) itself, which defines what kinds of data objects
of that type will be able to keep track of and the methods
that operate on that data. (Chapter 17.)
A collection of related types, grouped together under a
common label. Namespaces often represent broad
features of a program, and contain the types that are
needed to implement the feature. The types in a
namespace are typically placed together in a single folder
in the directory structure. (Chapters 3 and 27.)
Object-Oriented Programming
Name Collision
Operator
A situation where two different types use the same name.
At compile time, the compiler will be unable to determine
which of the two types is supposed to be used. To resolve
a name collision, you must either use fully qualified names
or an alias to rename one or both of the types involved in
the collision. (Chapter 27.)
A calculation or other work that is done by working with
one, two, or three operands. Many of the C# operators are
based on math operators, such as addition (+) or
multiplication (*). (Chapter 7.)
Name Hiding
When a variable in method or block scope has the same
name as an instance variable or static variable, making the
instance variable or static variable inaccessible. The
instance variable or static variable may still be accessed
using the this keyword. (Chapter 18.)
An approach to programming or programming languages
where code is packaged into chunks of reusable code that
have data and methods that act on that data, bundled
together. C# is an object-oriented programming language.
(Chapter 17.)
Operator Associativity
The rules that determine how operations of the same
precedence are evaluated when used together in a single
expression. Left-associative (or left-to-right associative)
operators are evaluated starting from the left side. Rightassociative (or right-to-left associative) operators are
evaluated starting from the right side. For example, in the
expression 5 – 3 – 1, the operations are evaluated like (5 –
Glossary
3) – 1, because subtraction is left-associative. (Operators
Chart, page 370 and Chapter 7.)
Operator Overloading
Within a type, providing a definition for what a particular
operator (+, -, *, /, etc.) should do. Not all operators are
overloadable. Operators should only be overloaded if
there is an intuitive way to use the operator. (Chapter 34.)
Optional Parameter
When a parameter is given a default value, allowing calling
methods to not provide a value for the parameter if
desired. (Chapter 28.)
Operator Precedence
derived class. In C#, in the base class, the method must be
either virtual or abstract, and in the derived class, the
method must be marked with override in order to
override the member. (Chapter 23.)
P/Invoke
See Platform Invocation Services.
Package
A set of resources (typically a single DLL file) along with
some additional metadata, that makes it easy to reference
and leverage a piece of code. In the .NET ecosystem,
packages are typically referenced and managed using the
NuGet Package Manager. (Chapter 46.)
The rules that determine which operator is evaluated first
in an expression where multiple operations are used.
Operators with higher precedence will be evaluated before
operators with lower precedence. For example,
multiplicative operations such as *, /, and % will be
executed before additive operations, such as + and -.
Operator precedence can be overruled by placing pieces of
the expression in parentheses. (Operators Chart, page 370
and Chapter 7.)
Parameter
Order of Operations
Parentheses
See Operator Precedence.
Out-of-Order Execution
For the purposes of optimization, the CPU may run
multiple instructions in parallel or in an order different
from program order. This is called out-of-order execution.
From the perspective of a single thread, all operations will
always function as though the operations were executed
in order. (Chapter 43.)
Overflow
When the result of an operation exceeds what the data
type is capable of representing. In a purely mathematical
world, this doesn’t happen, but on a computer, since all
types have a certain size with set ranges and limits,
overflow is when those bounds are surpassed. (Chapter 9.)
Overloading
Providing multiple methods with the same name but
different parameter list. Not to be confused with
overriding, an overload creates an entirely different
method, though usually, the purpose is to do similar things
with different inputs or parameters. For overloading
operators, see operator overloading. (Chapter 15.)
Overriding
Taking a method, property, or indexer in a base class and
providing an entirely different implementation of it in a
363
A type of variable that has method scope, meaning it is
available from anywhere inside of a method. Unlike other
local variables, the value contained in a parameter is
determined by the calling method, and may be different
for different method calls. (Chapter 15.)
Parent Class
See base class.
The symbols ( and ), used for order of operations, the
conversion operator, and method calls. (Chapters 7 and
15.)
Parse
The process of taking something and breaking it down into
smaller pieces that have individual meaning. Parsing is a
common task when reading data in from a file, or from
user input. (Chapter 29.)
Partial Class
A class that is defined in multiple files. This can be done to
separate a very large class into more manageable pieces,
or to separate a class into parts that are maintained by
separate things, such as the programmer and a GUI
designer tool. (Chapter 22.)
Pattern
A syntactic element that allows for conditional execution of
some code. It is composed of four parts. An input, which is
supplied to the pattern, a rule which is applied to the input
and is either “matched” (the rule returns true) or
unmatched, and if matched, an action is performed that
produces an output from the input. Patterns were
introduced to C# 7. Patterns can be used in switch
statements and with the is keyword. (Chapter 31.)
364
Glossary
See Fixed Statement.
were executed in program order. See also Out-of-Order
Execution and Volatile Fields. (Chapter 43.)
Platform Invocation Services
Project
Pinning
A mechanic whereby your C# code can directly invoke
unmanaged code that lives in another DLL that your
project references. (Chapter 42.)
Pointer
Contrasted with a reference, a pointer is a raw “link” to a
memory address. Pointers can only be used in unsafe
contexts. C# pointers are similar to pointers in other
languages such as C++. Pointers are generally only used
when interoperating with code that requires it. Generally
speaking, pointers should be avoided in favor of normal
reference types. (Chapter 42.)
Polymorphism
In C#, a class may declare a method or property, including
optionally providing an implementation for it, which can
then be overridden in derived classes in different ways.
This means that related types may have the same methods
implemented with different behavior. This ability to have
the same method that does different things when done by
different types is called polymorphism. The term comes
from Greek, meaning “many forms,” which reflects the fact
that these different types can behave differently, or do
different things, by simply providing different
implementations of the same method. (Chapter 23.)
Public
See accessibility level.
Precedence
See Operator Precedence.
Preprocessor Directive
Special commands embedded in source code that are
actually instructions for the compiler. (Chapter 43.)
Primitive Type
See Built-In Type.
Private
See accessibility level.
Procedure
See method.
Program Order
When referring to out-of-order execution, program order
is the order in which statements appear in the source code.
For optimization purposes, the CPU may not always
execute code in program order, but (assuming a single
thread) the effects will always be as though the instructions
In Visual Studio, a project represents the source code,
resource files, and settings needed to build a single
assembly (in the form of an .EXE file or a .DLL file). See also
solution and assembly. (Chapter 45.)
Property
A member that provides a way for the outside world to get
or set the value of a private instance variable, providing
encapsulation for it. This largely replaces any GetX or SetX
methods, providing simpler syntax, without needing to
publicly expose the data. The instance variable that a
property sets or returns is called a backing field, though
not all properties need a backing field. Properties do not
need to provide both a get and a set component, and when
they do, they do not need the same accessibility level.
Auto-generated properties can be used for very simple
properties that require no special logic. (Chapter 19.)
Protected
See accessibility level.
Query Expression
A special expression in C# that allows you to make SQL-like
queries on data within a C# program. Query expressions
are a fundamental part of LINQ. Query expressions are
composed of a variety of context-specific keywords and
structured into clauses that each manipulate, filter,
combine, or rearrange data sets to produce a final set of
results for the query (Chapter 38.)
Query Syntax
One of the two flavors of LINQ (contrasted with method
call syntax) that uses query expressions to perform queries
against data sets. (Chapter 38.)
Rectangular Array
A special form of multi-dimensional arrays where each row
has the same number of values. (Chapter 13.)
Recursion
Indicates a method that calls itself. Care must be taken to
ensure that eventually, a base case will be reached where
the method will not be called again, or you will run out of
space on the stack. See also recursion. (Chapter 15.)
Refactor
The process of tweaking or modifying source code in a way
that doesn’t affect the functionality of the program, but
improves other metrics of the code such as readability and
maintainability. (Chapter 45.)
Glossary
Reference
Right Associativity
A unique identifier for an object on the heap, used to find
an object that is located there. This is similar to a pointer,
used in other languages, which is a memory address
pointing to the object in question. However, a reference is
managed, and the actual object may be moved around in
memory without affecting the reference. (Chapter 16.)
See Operator Associativity.
Reference Semantics
When something has reference semantics, the identity of
the object is considered to be the object, rather than the
data it contains. When assigned from one variable to
another, passed to a method, or returned from a method,
while the reference is copied, it results in a reference to the
same object or data. This means both variables are
ultimately referencing the same data, and making changes
to one will affect the other. In C# all reference types have
reference semantics. See also reference type and value
semantics. (Chapter 16.)
Reference Type
One of the two main categories of types in C#. Reference
types are stored on the heap. A variable that stores a
reference type will actually contain a reference to the
object’s data. Reference types have reference semantics,
and as they are passed into a method or returned from a
method, a copy of the reference is made. The copy still
points to the same object. Modifying the object inside of
the method will affect the object that was passed in.
Classes are all reference types, as are the string and
object types, as well as arrays. See also value type, pointer
type, and reference. (Chapter 16.)
Reflection
The ability of a program to programmatically inspect types
and their members. This includes the ability to discover the
types that exist in an assembly and the ability to locate
methods and call them. Reflection allows you to sidestep
many of the rules that the C# language provides (such as
no external access to private variables or methods) though
performance is slower. (Chapter 43.)
Relational Operators
Operators that determine a relationship between two
values, such as equality (==), inequality (!=), or less than or
greater than relationships. (Chapter 10.)
Return
The process of going from one method back to the one
that called it. It is also used to describe the process of giving
back a value to the method that called it, upon reaching the
end of the method. In this sense, it is said to “return a
value” from the method. (Chapter 15.)
365
Run-Time Constant
See Constant.
Scientific Notation
The representation of very large or very small numbers by
expressing them as a “normal” number between 1 and 10,
multiplied by a power of ten. E.g., “1.3 x 1031.” In C# code,
this is usually expressed through E Notation. (Chapter 6.)
Scope
The part of the code in which a member (especially a
variable) or type is accessible. The largest scope for
variables is class or file scope, and it is accessible anywhere
within the type. Instance variables or class variables have
class scope. Method scope is smaller. Anything with
method scope is accessible from within the method that it
is used in, but not outside of the method. Parameters and
most local variables have method scope. The smallest
scope is block scope, which is for variables that are
declared within a code block such as a for loop. Variables
in block scope or method scope may have the same name
as something in class scope, which causes name hiding.
(Chapter 18.)
Sealed Class
A class that cannot be used as a base class for another
class. This is done by adding the sealed keyword to the
class definition. (Chapter 22.)
Signed Type
A numeric type that includes a + or - sign. (Chapter 6.)
Solution
In Visual Studio, a solution represents a collection of
related projects, and keeps track of how they reference
each other and other external assemblies. See also project,
assembly, and Solution Explorer. (Chapter 45.)
Solution Explorer
A window in Visual Studio that outlines the overall
structure of the code that you are working on. At the top
level is the solution you are currently working on. Inside of
that, any projects included in the solution are listed, and
inside of that are any files that the project uses, including
source code files, resource files, and configuration files.
Any hidden window can be opened up through the View
menu, or the View > Other Windows menu. (Chapter 45.)
366
Glossary
Source Code
Human-readable instructions that will ultimately be turned
into something the computer can execute. (Chapters 1 and
44.)
type, while a class is a reference type. Structs should be
immutable in most cases. (Chapter 21.)
Subclass
See derived class.
Square Brackets
The symbols [ and ], used for array indexing. (Chapter 13.)
Superclass
See base class.
Stack
One of two main parts of a program’s memory. The stack
is a collection of frames, which represent individual
method calls and that method’s local variables and
parameters. The stack is structured and is managed by
adding frames when a new method is called, and removing
frames when returning from a method. See also heap and
struct. (Chapter 16.)
Stack Allocation
Arrays are typically stored on the heap. A stack allocation
allows an array to be allocated on the stack instead, which
reduces pressure on the garbage collector, though the
array must be fixed size in this case. This can only be done
in an unsafe context, and only with local array variables.
(Chapter 42.)
Static
A keyword that is applied to members of a type to indicate
that they belong to the class as a whole, rather than any
single instance. A static member is shared between all
instances of the class, and do not need an instance to be
used. A type may define a single, parameterless static
constructor, which is executed just before the first time the
class is used. This static constructor provides initialization
logic for the type. If a type is static, all of its members must
be static as well. (Chapter 18.)
Static Type Checking
The act of verifying that members of a type that the code
uses exist at compile time. This ensures that incorrect or
invalid members such as methods, properties, and data
will exist or be caught at compile time. Catching type errors
at compile time is usually better than waiting until the
application is running, but this fails for dynamic objects
that change their members at run-time or whose members
aren’t known until run-time. Aside from variables that have
the dynamic type, all variables use static type checking in
C#. Static type checking is contrasted with dynamic type
checking, which happens at run-time. (Chapter 41.)
Subroutine
See method.
TAP
See Task-based Asynchronous Pattern.
Task-based Asynchronous Pattern
A pattern for achieving asynchronous programming (doing
other things while you wait for a long-running task to
complete) using tasks, primarily in the form of the Task
class and the generic variant Task<TResult>. (Chapter 40.)
Ternary Operator
An operator that works on three operators. C# only has
one ternary operator, which is the conditional operator.
(Chapter 10.)
Thread
A lightweight "process” (contained within a normal
process) which can be given its own code to work on, and
run on a separate core on the processor. Depending on
what you’re trying to do, running your code on multiple
threads may make your program run much faster, at the
cost of being somewhat more difficult to maintain and
debug. (Chapter 39.)
Thread Safety
Ensuring that parts of code that should not be accessed
simultaneously by multiple threads (critical sections) are
not accessible by multiple threads. (Chapter 39.)
Tuple
String
A simple data structure that stores a fixed number of
ordered (presumably related) items. There are two sets of
types in C# that allow you to use tuples. The Tuple classes
are reference types, and the ValueTuple structs are
reference types. ValueTuple is also used to leverage C# 7’s
syntax for returning multiple values from a method, which
is transformed into a ValueTuple behind the scenes.
(Chapters 28 and 46.)
A sequence of characters. In programming, you can usually
think of strings as words or text. (Chapter 6.)
Type
Struct
A custom-made type, assembled from other types,
including methods and other members. A struct is a value
A specific kind of information, as a category. A type acts as
a blueprint, providing a definition of what any object or
instance of the type will keep track of and store. C# has a
number of built-in types, but custom-made value or
Glossary
reference types can be defined with structs or classes
respectively. (Chapters 5 and 6.)
Typecasting
Converting from one type to another using the conversion
operator: int x = (int)3.4; (Chapter 9.)
367
Unsigned Type
A type that does not include a + or - sign (generally
assumed to be positive). (Chapter 6.)
User-Defined Conversion
The ability of the C# compiler to guess the type involved in
certain situations, allowing you to leave off the type (or use
the var type). (Chapter 6.)
A type conversion mechanism that is defined by the
programmer instead of the language. C# allows you to
create implicit and explicit conversions for your types, but
it is recommended that you use them sparingly, as they
have a variety of complications that often arise from using
them. See also typecasting. (Chapter 43.)
Type Safety
Using Directive
Type Inference
The ability of the compiler and runtime to ensure that
there is no way for one type to be mistaken for another.
This plays a critical role in generics. (Chapter 25.)
Unary Operator
An operator that works with only a single value, such as the
negation operator (the – sign in the value “-3”). (Chapter 7.)
Unchecked Context
A section of code wherein mathematical overflow will wrap
around instead of throwing an exception. An unchecked
context is the default. (Chapter 43.)
A special statement at the beginning of a C# source file,
which identifies specific namespaces that are used
throughout the file, to make it so you do not need to use
fully qualified names. (Chapters 3 and 27.)
Using Statement
Not to be confused with using directives, a using
statement is a special way of wrapping up an object that
implements the IDisposable interface, which disposes of
the object when leaving the block of code that it wraps.
(Chapter 43.)
Value Semantics
An app model designed to support GUI applications in the
.NET Framework and .NET Core stacks. Sometimes
abbreviated UWP. (Chapters 44 and 51.)
Something is said to have value semantics if its value is
what counts, not the thing’s identity. When something has
value semantics and is assigned from one variable to
another, passed to a method as a parameter, or returned
from a method, the value is copied, and while the two
variables or sides will have the same value, they will be
copies of the same data. Modifying one will not affect the
other, because a copy was made. In C#, all value types have
value semantics. See also value types and reference
semantics. (Chapter 16.)
Unsafe Code
Value Type
Underflow
With floating point types, when an operation results in loss
of information, because the value was too small to be
represented. (Chapter 9.)
Universal Windows Platform
Unsafe code is not fully managed by the CLR, giving you
some added abilities like better performance or calling
native code, at the cost of giving up some of the benefits of
the CLR. Pointer types can only be used in an unsafe
context. Unsafe code is only rarely needed. (Chapter 42.)
Unsafe Context
An unsafe context is a region of code (a type, a method, or
a section of a method) where unsafe code can be
performed. This allows you to leave the CLR’s managed
memory framework and work more directly with memory
addresses, etc. This is generally only used in the context of
interoperating with external, non-C# code (like C or C++).
(Chapter 42.)
One of two main categories of types in C#. Value types are
stored on the stack when possible (when not a part of a
reference type). Value types follow value semantics, and
when they are passed to a method or returned from a
method, are copied. Structs are value types, as are all of
the built-in types except object and string. See also
reference type and pointer type. (Chapter 16.)
ValueTuple
See Tuple.
Variable
A place to store data. Variables are given a name, which
can be used to access the variable, a type, which
determines what kind of data can be placed in it, and a
value, which is the actual contents of the variable at any
368
Glossary
given point in time. Once a variable is created, its name and
type cannot be modified, though its value can be. Variables
come in a number of different varieties, including local
variables, instance variables, parameters, and static class
variables. (Chapter 5.)
Virtual Machine
A special piece of software that can run executable code.
The CLR is the virtual machine that .NET uses. Virtual
machines usually provide controlled hardware access to
the software that they are running and perform a number
of useful features for the code that it runs, including a
security model and memory management. Virtual
machines are also usually responsible for the task of JIT
compiling code to machine code at run-time. (Chapter 44.)
Virtual Method
If a method is virtual, derived classes are allowed to
override the method and provide a new definition for it. To
mark a method as virtual, the virtual keyword should be
added to it. See also abstract method and overriding.
(Chapter 23.)
Visual Basic.NET
A programming language that is very different from C# in
syntax, but has almost a 1-to-1 correspondence in
keywords and features, because both were designed for
.NET. (Chapter 44.)
Visual C++
See C++.
Visual Studio
Microsoft’s IDE, designed for making programs in C# and
other programming languages. (Chapters 2 and 45.) The
latest version of Visual Studio is Visual Studio 2017.
Visual Studio 2017 Community Edition
A member of the Visual Studio 2017 family that is free and
has identical features to Professional. It allows commercial
development as long as you have no more than 5
developers, you don’t have more than 250 computers, and
your company doesn’t gross more than $1,000,000 in
revenue. There are additional exceptions that allow you to
use this free, including for educational use and open
source use. This is the version that most new C#
developers will start with.
Visual Studio 2017 Professional
A member of the Visual Studio family that has identical
features to 2017 Community, but comes at cost. If you have
more than 5 Visual Studio users in your company, you have
more than 250 computers in the company, or the company
has gross revenue in excess of $1,000,000, then the
company will have to find the money to pay for this.
Volatile Field
An instance variable that has been protected with memory
barriers by using the volatile keyword. This is used to
ensure that data accessed by multiple threads, but not
protected by some other thread-safe mechanism will be
accessed in a way that won’t have any unintended
consequences from out-of-order execution at the
hardware level. (Chapter 43.)
Windows Forms
An older app model designed for creating GUI applications.
This app model has largely been replaced by Windows
Presentation Foundation. (Chapters 44 and 51.)
Windows Presentation Foundation
An app model designed for creating GUI applications. It is
a part of the .NET Framework stack. This is frequently
abbreviated WPF. This is generally preferred to Windows
Forms. (Chapters 44 and 51.)
Xamarin
A .NET Platform stack designed primarily for mobile
development (specifically iOS and Android). (Chapter 44.)
XML Documentation Comment
A special type of comment, placed immediately above a
type or member of a type to define what it does and how
it is used. These comments are started by using three
forward slashes (///). The format can include certain XML
tags. XML documentation comments are used by
automated tools to generate documentation that can be
displayed on websites for anyone else who uses your code.
It is also used by Visual Studio to create IntelliSense for
your code. (Chapter 15.)
Tables and Charts
Operators
Page
Overloadable?
Associativity
Arity
Member Access
Name
Syntax
x.y
Accesses members of an instance, type, namespace, etc.
Description
84
No
Left-to-Right
2
Null Conditional Member Access
x?.y
Member access with a null check first
284
No
Left-to-Right
2
Function Invocation
x(y)
Invoking or calling a method
88
No
Left-to-Right
2
Aggregate Object Indexing
a[x]
Accessing items in a collection
78
Indexers
Left-to-Right
2
Null Conditional Indexing
a?[x]
Collection access with a null check first
284
Indexers
Left-to-Right
2
Postfix Increment
x++
Adds 1 to a variable (original value returned)
58
Yes
Left-to-Right
1
Postfix Decrement
x--
Subtracts 1 from a variable (original value returned)
58
Yes
Left-to-Right
1
Type Instantiation
new
78
No
Right-to-Left
1
Creates new objects by invoking a constructor
Category
Primary Operators
(These Happen First)
Typeof
typeof(T)
Produces a Type object from the name of a type
280
No
Right-to-Left
1
Checked
checked
Switches to a checked context for overflow
287
No
Right-to-Left
1
Unchecked
Switches to an unchecked context for overflow
287
No
Right-to-Left
1
Default
default(T)
Produces the default value of a given type
171
No
Right-to-Left
1
Delegate
delegate
Defines delegate types or produces a new empty instance
206
No
Right-to-Left
1
Sizeof
sizeof(T)
Produces the size of a given type
276
No
Right-to-Left
1
Pointer Dereference
unchecked
Member access through a pointer (unsafe context)
266
No
Left-to-Right
2
Unary Plus
+x
Produces the same value as the operand
45
Yes
Right-to-Left
1
Unary Minus/Numeric Negation
-x
Produces the negative of the operand
45
Yes
Right-to-Left
1
Logical Negation
!x
Produces the Boolean inverse of the operand
65
Yes
Right-to-Left
1
Bitwise Complement
~x
Produces the bitwise complement of the operand
278
Yes
Right-to-Left
1
Prefix Increment
++x
Adds 1 to a variable (incremented value returned)
58
Yes
Right-to-Left
1
Prefix Decrement
--x
Subtracts 1 from a variable (decremented value returned)
58
Yes
Right-to-Left
1
Type Casting
(T)x
Specifies a cast or type conversion
55
User-Defined Conversions
Right-to-Left
1
Awaits asynchronous tasks
256
No
Right-to-Left
1
Await
x->y
await
Unary Operators
Address Of
&x
Produces the address of a variable (unsafe context)
266
No
Right-to-Left
1
Dereferencing/Indirection
*x
Declare pointer types and dereference pointers
266
No
Right-to-Left
1
43
Yes
Left-to-Right
2
43
Yes
Left-to-Right
2
Multiplication
x*y
Multiplies two values
Division
x/y
Divides the first value by the second
Remainder/Modulus
x%y
Produces the remainder of a division operation
44
Yes
Left-to-Right
2
Addition
x+y
Adds two values
43
Yes
Left-to-Right
2
Subtraction
x–y
Subtracts the second value from the first
43
Yes
Left-to-Right
2
278
Yes
Left-to-Right
2
278
Yes
Left-to-Right
2
63
Yes
Left-to-Right
2
Left Shift
x << y
Bitwise shifts a value a number of bits leftward
Right Shift
x >> y
Bitwise shifts a value a number of bits rightward
Less Than
x<y
Returns whether x is less than y
Multiplicative Operators
Additive Operators
Shift Operators
Tables and Charts
371
Greater Than
x>y
Returns whether x is greater than y
63
Yes
Left-to-Right
2
Less Than Or Equal
x <= y
Returns whether x is less than or equal to y
63
Yes
Left-to-Right
2
Greater Than Or Equal
x >= y
Returns whether x is greater than or equal to y
Relational and Type Testing
Operators
63
Yes
Left-to-Right
2
Is
is
Determines if a value is a certain type or matches a pattern
146
No
Right-to-Left
1
As
as
Type conversion, returns null where (T)x throws an exception
147
No
Right-to-Left
1
63
Yes
Left-to-Right
2
63
Yes
Left-to-Right
2
Equality
x == y
Returns whether x exactly equals y
Inequality
x != y
Returns whether x does not equal y
Logical AND
x&y
Performs bitwise AND between two values
Logical AND Operator
278
Yes
Left-to-Right
2
Logical XOR
x^y
Performs bitwise XOR between two values
Logical XOR Operator
278
Yes
Left-to-Right
2
Logical OR
x|y
Performs bitwise OR between two values
Logical OR Operator
278
Yes
Left-to-Right
2
Conditional AND Operator
66
No
Left-to-Right
2
Equality Operators
Conditional AND
x && y
Returns whether both x and y are true
Conditional OR
x || y
Returns whether either x, y, or both are true
Conditional OR Operator
66
No
Left-to-Right
2
Null Coalescing
x ?? y
Returns x unless it is null, otherwise returns y
Null Coalescing Operator
283
No
Right-to-Left
2
Conditional Operator
67
No
N/A
3
Conditional/Ternary
t?x:y
If t is true, produces x, otherwise y is produced
Assignment
x=y
Assigns the value of y to the variable x
46
No
Right-to-Left
2
Addition Assignment
x += y
Shorthand for x = x + y
47
Indirectly
Right-to-Left
2
Subtraction Assignment
x -= y
Shorthand for x = x – y
47
Indirectly
Right-to-Left
2
Multiplication Assignment
x *= y
Shorthand for x = x * y
47
Indirectly
Right-to-Left
2
Division Assignment
x /= y
Shorthand for x = x / y
47
Indirectly
Right-to-Left
2
Remainder Assignment
x %= y
Shorthand for x = x % y
AND Assignment
x &= y
Shorthand for x = x & y
OR Assignment
x |= y
XOR Assignment
x ^= y
Assignment and Lambda
Operators
(These Happen Last)
47
Indirectly
Right-to-Left
2
278
Indirectly
Right-to-Left
2
Shorthand for x = x | y
278
Indirectly
Right-to-Left
2
Shorthand for x = x ^ y
278
Indirectly
Right-to-Left
2
Left Shift Assignment
x <<= y
Shorthand for x = x << y
278
Indirectly
Right-to-Left
2
Right Shift Assignment
x >>= y
Shorthand for x = x >> y
278
Indirectly
Right-to-Left
2
Defines a lambda
230
No
Left-to-Right
2
Lambda Declaration
=>
Keywords
Name
Reference
Name
Reference
Name
31
ulong
Reference
abstract
154
int
add ♦
218
interface
157
unchecked
294
alias ♦
293
internal
121
unsafe
265
as
147
into ♦
242
ushort
32
ascending ♦
240
is
146
using
async ♦
256
join
240
value ♦
256
await ♦
32
16
125
let
240
var ♦
40
base
147, 153
lock
249
virtual
151
bool
36
long
break
74
nameof
by
241
namespace
byte
32
new
case
68
null
101
while
catch
196
object
148
yield ♦
char
33
operator
219
checked
294
orderby ♦
240
class
112
out
183
const
273
out (generics)
292
continue
74
override
151
decimal
34
params
182
default
68, 171
partial (method)
149
32
275
void
89
volatile
296
16
where (query clause) ♦
239
78
where (generics) ♦
169
delegate
206
partial (type)
149
descending ♦
240
private
114
do
73
protected
148
double
34
public
114
dynamic ♦
260
readonly
273
else
62
ref
183
enum
83
remove ♦
218
event
212
return
explicit
286
sbyte
32
extern
270
sealed
148
false
36
select
239
finally
199
set
125
fixed
268
short
32
float
34
sizeof
276
for
73
stackalloc
267
foreach
81
static
120
from ♦
238
string
36
get ♦
125
struct
138
global ♦
293
switch
goto
287
this
117
group ♦
241
throw
197
if
61
true
implicit
286
try
196
in
81
typeof
280
in (generics)
292
uint
71
272
89
68
36
32
♦ = Contextual Keyword, only reserved in certain
contexts.
Tables and Charts
373
Built-In Types
Type
Category
Size (Bytes)
Range
byte
Value
1
0 to 255
short
Value
2
-32,768 to +32,767
int
Value
4
-2147,483,648 to 2,147483,647
long
Value
8
-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
-128 to +127
sbyte
Value
1
ushort
Value
2
0 to 65,535
uint
Value
4
0 to 4,294,967,295
ulong
Value
8
0 to 18,446,744,073,709,551,615
Precision
float
Value
4
±1.0e-45 to ±3.4e38
7
double
Value
8
±5e-324 to ±1.7e308
15-16
decimal
Value
16
±1.0 × 10e-28 to ±7.9e28
28-29
char
Value
2
U+0000 to U+ffff (All Unicode characters)
1
bool
Value
object
Reference
true/false
string
Reference
class
Reference
arrays
Reference
enum
Value
Same as underlying type (default 4)
struct
Value
Size of content varies.
Custom value type
pointer
Pointer
4
Pointers to other types
Any type of data
Size of content varies.
References are 4 bytes.
Text of any length
Custom reference type
Collection of any other type
Listed enumeration members
53 Index
Symbols
- operator, 43, 219
π, 57
-- operator, 58, 219
-= operator, 47, 216, 219
. operator, 85, 220
!= operator, 64, 219
% operator, 44, 219
%= operator, 47, 219
& operator, 278
:: operator, 293
&& operator, 67, 220, 278
&= operator, 279
& operator, 267
* operator, 43, 219, 266
*= operator, 47, 219
/ operator, 43, 219
/= operator, 47, 219
: operator, 145, 158
?: operator, 68
?? operator, 283
@ symbol, 52
[] operator, 223
^ operator, 279
^= operator, 279
| operator, 278
|| operator, 67, 220, 278
|= operator, 279
~ operator, 279
+ operator, 43, 219
++ operator, 58, 219
+= operator, 47, 216, 219
< operator, 64, 219
<< operator, 278
<<= operator, 279
<= operator, 65, 219
= operator, 47, 220
== operator, 62, 219
=> operator, 232
> operator, 64, 219
-> operator, 267
>= operator, 65, 219
>> operator, 278
>>= operator, 279
.NET Core, 310
.NET Framework, 4, 309, 354
.NET Platform, 301
.NET Standard Library, 4, 302, 308
Index
A
abstract class, 151, 354
abstract keyword, 154
abstract method, 158, 354
accessibility level, 127, 354
internal, 121, 355
private, 114, 355
protected, 148, 355
protected internal, 355
public, 115, 355
accessibility modifier. See accessibility level
Action delegate, 216
addition, 43
Address Of operator, 267
algorithm, 21, 352
alias, 178
Android app model, 312
anonymous method, 231, 355
app model, 4, 303, 311
application virtual machine, 306
argument. See parameter
ArgumentNullException class, 198
array, 78, 144, 346, 355
declaring, 79
elements, 79
length, 80
retrieving and assigning values in, 79
as keyword, 147
ASP.NET, 311, 351, 355
assembler, 305
assembly, 12, 319, 355
assembly language, 305, 355
assembly reference, 331
assignment, 355, 361, 365
assignment operator, 46
associativity, 46
async keyword, 256
asynchronous programming, 251, 355
AsyncResult pattern, 254
with the Task-based Asynchronous Pattern, 255
with the ThreadPool, 253
asynchrony. See asynchronous programming
attribute, 274, 355
await keyword, 256
B
backing field, 126
base case, 96
base class, 145, 355
Base Class Library, 4, 302, 309, 355
base keyword, 148, 153
BCL. See Base Class Library
binary, 3, 14, 356
binary language, 305
binary literal, 39
BinaryWriter class, 193
bit, 29
bit field, 277, 356
bitshift operator, 278
bitwise and operator, 278
bitwise complement operator, 279
bitwise logical operator, 278
bitwise operator, 356
bitwise or operator, 278
block scope, 116
bool type, 36, 66, 142
Boolean, 356
Boolean struct, 142
break keyword, 70, 75
breakpoint, 336, 356
built-in type, 31, 142, 356
byte, 29
Byte struct, 142
byte type, 32, 142
C
C++, 4, 356
Caesar cipher, 346
call stack, 335
callback method, 255
case keyword, 70
case label. See case statement
case statement, 70
casting. See typecasting
catch block, 196
catch keyword, 196
Char struct, 142
char type, 33, 142
checked context, 294
CIL. See Common Intermediate Language
class, 17, 112, 356
creating, 112
partial, 149
sealed, 148
class keyword, 113
class scope, 116
class variable. See static class variable
CLR. See Common Language Runtime
Code Window, 313, 356
command line arguments, 285, 356
comment, 19, 356
Common Intermediate Language, 302, 307, 356
Common Language Runtime, 4, 302, 306, 357
compiler, 14, 357
compiler error, 326
compiler warning, 326
compile-time contant. See constant
compound assignment operator, 47, 357
conditional operator, 67, 68, 357
375
376
Index
Console class, 95
const keyword, 273, 357
constant, 273, 357
run-time constant, 357
constant pattern, 202
constructor, 115, 357
default parameterless, 140
default parameterless constructor, 357
inheritance, 147
context switch, 246
continue keyword, 75
contravariance, 290
Convert class, 49, 95, 192
covariance, 290
critical section, 357
.csproj file, 341
.csproj.user file, 341
CSV file, 191
curly braces, 16, 64, 328, 357
D
data structure, 352
debug, 333, 357
debug mode, 15, 333
Decimal struct, 142
decimal type, 35, 142
declaration, 357
decrement, 357
default keyword, 70, 171
delegate, 206, 214, 231, 357
chaining, 209
creating, 206
relation to events, 218
using, 207
Delegate class, 208, 254
delegate keyword, 207
Delta Engine, 352
dependency, 319
derived class, 145, 357
Dictionary class, 160, 165
digit separator, 40
DirectX, 352
divide and conquer, 87, 358
DivideByZeroException class, 198
division, 43
division by zero, 56, 358
DLL, 319, 358
DLLImport attribute, 270
do keyword, 74
Double struct, 142
double type, 34, 142
do-while loop, 74
dynamic keyword, 260
dynamic language runtime, 260
dynamic object, 260
dynamic objects, 259
dynamic type checking, 259
dynamic typing, 259
DynamicObject class, 262
E
e (number), 57
E notation, 38
else keyword, 63
encryption, 346
enum. See enumeration
enum keyword, 85
enumeration, 358
flags, 279
underlying type, 86
error handling, 194
Error List, 315, 326, 358
escape character, 51
escape sequence, 51
event, 157, 212, 358
attaching and detaching, 215
defining, 213
raising, 214
relation to delegates, 218
event keyword, 214
EventArgs type, 214
EventHandler delegate, 214
EventHandler<TEventArgs> delegate, 216, 217
events, 253
exception, 194, 334, 358
catching, 195
filters, 200
throwing, 195
Exception class, 195
exception filter, 200
exclusive or operator, 279
EXE, 319, 358
ExpandoObject class, 262
explicit, 358
explicit conversion, 55
explicit keyword, 286
expression, 44
expression lambda, 233
expression-bodied members, 234
extension method, 226, 358
extern keyword, 270
F
factorial, 96
false keyword, 36
Fibonacci sequence, 97
field. See instance variable
File class, 190
file I/O, 157, 190
file input, 190
file output, 190
FileStream class, 192
Index
fixed keyword, 269
fixed size array, 269
fixed statement, 268
FizzBuzz, 77
Flags attribute, 279
float type, 34, 142
floating point number, 34
floating point type, 359
for keyword, 74
for loop, 74
foreach keyword, 82
foreach loop, 77, 82, 272
forever loop, 73
FormatException class, 198
frame, 99
Framework Class Library, 359
from clause, 238
fully qualified name, 176, 359
Func delegate, 211
function. See method
G
game development, 352
garbage collection, 79, 99, 359
generic method, 171
generic type parameter, 163
generics, 160, 167, 359
covariance and contravariance, 290
inheritance, 290
motivation for, 160
get keyword, 126
global keyword, 293
goto keyword, 287
graphical user interfaces, 311
group clause, 241
group join, 242
GUI development, 311
H
heap, 98, 359
Hello World!, 10
hexadecimal literal, 39
I
IAsyncResult interface, 254
IDE. See Integrated Development Environment
IDisposable interface, 280
IDynamicMetaObjectProvider interface, 261
IEnumerable interface, 83
IEnumerable<T> interface, 164, 237
if keyword, 61
if statement, 61
immutability, 141, 360
implicit, 359
implicit conversion, 55
implicit keyword, 286
implicitly typed local variable, 360
in keyword, 83
increment, 359
index, 79
index initializer syntax, 225
indexer, 157, 220, 223, 359
multiple indices, 224
types in, 224
IndexOutOfRangeException class, 198
indirection operator, 267
infinite loop, 73
infinity, 56
inheritance, 144, 360
instance variable, 114, 360
int type, 31, 142
Int16 struct, 142
Int32 struct, 142
Int64 struct, 142
integer division, 54, 360
integer type, 33
integral type, 33, 360
Integrated Development Environment, 6, 360
IntelliSense, 316, 360
interface, 150, 156, 360
creating, 157
implementing, 158
implementing multiple, 159
naming convention, 158
interface keyword, 158
internal keyword, 121
into clause, 242
InvalidCastException class, 198
invariance, 290
iOS app model, 312
is keyword, 146
patterns, 204
is-a relationship, 145, 360
is-a-special-type-of relationship, 145, 360
iterator, 360
J
jagged array, 82, 360
Java, 4, 351, 361
JIT compiler. See Just-in-Time compiler
join clause, 240
Just-in-Time compiler, 306, 361
K
keyword, 16, 361
377
378
Index
L
N
labeled statement, 287
lambda expression, 230, 361
multiple parameters, 233
zero parameters, 233
lambda operator, 232
Language Integrated Query, 236, 361
lazy evaluation, 67
left associativity, 46
let clause, 240
library assembly, 13
line numbering, 316
lines of code, 229
LINQ. See Language Integrated Query
Linux, 9
List class, 231
literal, 35, 38
local function, 180
local variable, 114, 361
lock keyword, 250
long type, 32, 142
loop, 72, 361
breaking out of, 75
continuing to next iteration, 75
name collision, 178, 362
name hiding, 117, 362
named parameter, 182, 362
nameof operator, 275
namespace, 17, 175, 330, 342, 362
namespace alias operator, 293
namespace keyword, 16, 175
NaN, 57, 362
narrowing conversion, 55
nested statements, 67
nesting, 76, 362
new keyword, 79, 154
NotImplementedException class, 198
NotSupportedException class, 198
NuGet package manager, 321
null keyword, 102
null propagation, 283
null propagation operators, 283
null reference, 102, 362
nullable type, 283, 362
Nullable<T> struct, 283
NullReferenceException class, 198, 214
numeric literal, 38
M
O
managed memory, 99, 361
math, 42
Math class, 57
member, 17, 88, 109, 114, 213, 361
memory barrier, 296
method, 16, 87, 157, 180, 361
calling, 89
local function, 180
multiple return values, 11, 186
passing parameters to, 92
returning from, 90
signature, 94
method body. See method implementation
method call, 361
method implementation, 89, 362
method overloading, 93, 331
method overriding, 152
method scope, 116
method signature, 362
Microsoft Developer Network, 353
mod, 44
modulo operator, 44
MonoGame, 352
MulticastDelegate class, 208
multi-dimensional array, 82
multiple inheritance, 150, 159
multiplication, 43
mutex, 250, See mutual exclusion
mutual exclusion, 250, 362
object, 362
Object class, 142
object initializer syntax, 128
object keyword, 148
object type, 142, 247
object-oriented programming, 105, 362
Obsolete attribute, 274
off-by-one error, 79
OpenGL, 352
operand, 43
operation, 43
operator, 43, 362
binary, 43, 68, 356
ternary, 68, 366
unary, 45, 68, 367
operator keyword, 221
operator overloading, 219, 223, 363
operator precedence, 46
opereator associativity, 46
optional parameter, 181, 363
Options Dialog, 315
order of operations, 46, 363
orderby clause, 240
out keyword, 183
out-of-order execution, 295
output parameter, 184, 187
overflow, 57, 363
overloading, 93, 363
override, 152
Index
override keyword, 152
P
package, 319
parameter, 92, 114, 363
variable number of, 182
parameter list, 92
ParameterizedThreadStart delegate, 247
params keyword, 183
parent class. See base class
parentheses, 46, 328, 363
parse, 191, 363
partial class, 363
partial keyword, 149
pattern
in switch statement, 203
with is keyword, 204
pattern matching, 201
pinned objects, 269
pointer, 266
pointer member access operator, 267
pointer type, 266, 364
polymorphism, 151, 364
postfix notation, 58
precedence, 46
prefix notation, 58
preprocessor directive, 281, 364
primitive type. See built-in type
private keyword, 114
procedure. See method
process assembly, 13
process virtual machine, 306
project, 12, 340, 364
Properties Window, 315
property, 124, 157, 223, 364
auto-implemented, 127
default value, 128
readonly, 127
protected keyword, 148
Q
query expression, 236, 364
R
Random class, 108
readonly keyword, 274, 357
real number, 34
rectangular array, 82, 364
recursion, 96, 364
ref keyword, 183
ref local variable, 185
refactor, 317, 364
reference, 100, 365
reference parameter, 184
reference semantics, 103, 365
reference type, 98, 100, 110, 365
reflection, 280, 365
regular expression, 201
relational operator, 64, 221, 365
release mode, 15, 333
remainder, 44
return, 59, 66, 90, 272, 329, 365
return keyword, 199
right associativity, 46
S
SByte struct, 142
sbyte type, 33, 142
scalar, 221
scientific notation, 38
scope, 233, 327, 365
sealed class, 365
sealed keyword, 148
select clause, 239
semicolon, 18
set keyword, 126
SharpDX, 352
short type, 32, 142
signature, 94
signed type, 32, 365
Single struct, 142
sizeof operator, 276
.sln file, 340
software engineering, 352
solution, 13, 340, 365
Solution Explorer, 314, 365
source code, 3, 366
square array, 82
square brackets, 366
stack, 98, 366
stack allocation, 267
stack trace, 99
StackOverflowException class, 198
statement lambda, 233
static, 366
static class, 121, 227
static class variable, 120
static constructor, 121
static keyword, 120, 227, 366
static method, 227
static type checking, 259
statically typed language, 259
String class, 142
string concatenation, 52
string type, 36, 142, 366
string.Split method, 192
struct, 138, 145, 366
379
380
Index
structure, 138
subclass, 145
subroutine. See method
subscript, 79
subtraction, 43
succinct null checking, 283
.suo file, 340
superclass, 145
switch keyword, 70
switch statement, 69
patterns, 203
types allowed, 71
synchronous programming, 252
event-based, 253
T
TAP. See Task-based Asynchronous Pattern
Task class, 255
Task<TResult> class, 255
Task-based Asynchronous Pattern, 255
TextReader class, 193
TextWriter class, 192
this keyword, 117, 227
thread, 245, 366
sleep, 247
Thread class, 246, 252
thread safety, 249, 250, 366
ThreadPool class, 253
ThreadStart delegate, 246
throw keyword, 197
true keyword, 36
try block, 196
try keyword, 196
type, 26, 366
Type class, 280
type conversion, 329
type inference, 40, 233, 367
type pattern, 202
type safety, 162, 367
type system, 31
typecasting, 55, 86, 146, 161, 286, 329, 367
typeof keyword, 280
U
uint type, 32, 142
UInt16 struct, 142
UInt32 struct, 142
UInt64 struct, 142
ulong type, 32, 142
unchecked context, 294
underflow, 57, 367
unhandled exception, 195
Unicode, 33
Unity, 352
unsafe code, 265, 367
unsafe context, 266
unsafe keyword, 266
unsigned type, 367
unverifiable code, 266
user input, 48
user-defined conversion, 286, 367
ushort type, 32, 142
using directive, 16, 175, 227, 330, 367
static, 179
using keyword, 16, 178
using statement, 280, 367
V
value keyword, 126
value semantics, 103, 367
value type, 98, 100, 367
ValueTuple class, 188
ValueTuple struct, 321
var keyword, 41
var pattern, 203
variable, 25, 367
assigning a value to, 27
declaring, 26
naming, 29
verbatim string literal, 52
version control system, 340
virtual keyword, 152, 158
virtual machine, 306, 368
advantages, 307
disadvantage, 308
virtual method, 151, 368
Visual Basic.NET, 368
Visual C++. See C++
Visual Studio, 6, 7, 11, 313, 368
installer, 7
keyboard shortcuts, 317
Visual Studio Code, 9
Visual Studio Community Edition, 7, 368
Visual Studio Enterprise Edition, 7
Visual Studio Professional Edition, 7, 368
void keyword, 89
volatile fields, 295
volatile keyword, 295
W
where clause, 239
where keyword, 170
while keyword, 72
while loop, 72
whitespace, 17, 64
widening conversion, 55
Windows Forms, 311, 351, 368
Windows Presentation Foundation, 368
word count, 228
WPF, 351
Index
X
Xamarin, 9, 303, 310, 352
Xamarin Studio, 9
Xenko, 352
XML Documentation Comment, 20, 95, 368
XNA, 352
xor operator, 279
381
It is illegal to redistribute this digital book.
Please do not share this file via email, websites,
or any other means. Be mindful about where
you store it and who might gain access to it.
The digital format of this book is only
distributed via https://gumroad.com/l/CbfL. If
you have received this book through any other
means, please report it to
rbwhitaker@outlook.com.
Download