It's dangerous to code alone! Take this.

Errata

Page 23: Typo: “While we do could this”

In the second paragraph under the Expressions subsection, there is a sentence that reads, “While we do could this:”, but should read “While we could do this:”.

The area of a trapezoid code sample has the Wikipedia link on its own line, but that would make it not compile. Ultimately, this was just a line that soft wrapped when it shouldn’t have. It could be fixed by ensuring it stays on the line before it. It could also be fixed by adding // immediately before it, to make sure that the compiler doesn’t try to parse it.

Page 244: Additional Information: Second workaround for foreach loops and lists

The section that describes how you can use foreach with lists points out that you can’t modify a list while in the middle of iterating over it with a foreach loop. The book states that there are a two workarounds for this, and describes one of them.

If you want to see the other, you can read the article about lists and InvalidOperationExceptions.

Page 265: Correction: Swap method is wrong

The code for the ref-based Swap method has the following:

int temporary = a;
a = b;
b = a;

That’s wrong. It should read:

int temporary = a;
a = b;
b = temporary;

Page 266: Correction: Code missing $

The code at the bottom of page 266 has this line:

    Console.WriteLine("You entered {value}.");

This is supposed to interpolate the variable value, but is missing a $, so it will print out a literal You entered {value}. instead of something like You entered 4..

To correct this, this string should start with a $:

    Console.WriteLine($"You entered {value}.");

Page 267: Correction: Method to use in challenge not clear

The Safer Number Crunching challenge calls out using the static int.TryParse(out int value) method, but there is no method with just a single int parameter.

The intended method to use here has a string for the first parameter, followed by that int output parameter: int.TryParse(string s, out int result). Use this one for the challenge.

(This particular level shows several examples using this method, so I hope this one isn’t too confusing to people.)

Page 268: More Information: Extension methods can be applied to all types, not just those listed

The first sentence in the section called Extension Methods lists some types that extension methods can be applied to. It specifically calls out classes, structs, enumerations, and interfaces. It does not specifically call out records, but those work too.

Page 268: Minor Improvement: ToUpper and ToLower vs. lowercase and uppercase

This is a very minor thing, but it would be better to keep the ordering consistent. Instead of, “…the ToUpper and ToLower methods that produce lowercase and uppercase versions of the string,” it would be better to say “… the ToUpper and ToLower methods that produce uppercase and lowercase versions of the string.”

Page 269: Typo: Numbering on answers for the knowledge check

There is a typo in the numbering on the answers for the knowledge check on page 269. In particular, the last item is numbered with a 6, but should be an 8.

Page 273: Correction: Exception handler, not event handler

The book states “When looking for an event handler, the order matters.” But this should read, “When looking for an exception handler, the order matters.”

Event and exception handlers are both C# concepts, but in this level, it is exception handlers that matter.

Page 282: Consistency: AddOne and NumberDelegate

This isn’t technically wrong, but is not as consistent as it ought to be.

The Speedrun has an example with NumberDelegate with two parameters, then assigns it a method named AddOne, which would be more logical as a delegate with only one parameter. On the line below that, it invokes it with two parameters. Then in the level, a single-parameter NumberDelegate is used.

For consistency, it would be better to treat the NumberDelegate defined in the Speedrun in the same way it is defined on page 283, with just a single parameter, and then invoke it on the fourth bullet point with a single parameter instead of two.

Delegates can use any number of parameters, so there is nothing technically wrong with what is stated here, it is just clearer with everything being single-parameter delegates in the Speedrun.

Page 282: Typo

“They may not seem like a big deal…” –> “That may not seem like a big deal…”

Pages 289 and 290: Minor Improvement: Health <= 0 instead of < 0

There are four places on these two pages where the book raises a ShipExploded event when Health < 0. It’s not impossible that a game could treat “dead” as health under zero, but it’s not common. All four of those places would be better if they checked if Health <= 0.

Page 290: Correction: Code example doesn’t compile

The code sample on page 290 does not compile as the book is written. It calls PlaySound with a string and a float, but the method defined in that sample doesn’t have a float volume parameter.

This could be fixed by calling it without the CalculateVolume result or by changing the signature to allow the float parameter. I think the second one makes more sense, because otherwise, there’s not a lot of value in the event including the location as a parameter either, which was the point of the sample in the first place.

Page 292: More Information: Nuances of custom event accessors

The 3rd edition called out something that inadvertently got dropped in the 4th edition. When you make custom event accessors, there is one thing you need to know about it: You cannot invoke the event directly when you do custom event accessors (with the add and remove accessors). Instead, you must invoke the delegate behind the event, if one exists. (That last part is why you can’t invoke/raise the event directly. The compiler does not know which delegate–or indeed, if there even is a delegate or perhaps even multiple delegates–underlies the event, so it puts that burden on the programmer.)

Page 301: Clarity: WriteAllLines allows any string collection, not just string[]

The book calls out that you use a string[] instead of a single string, and then in the code sample uses List<string> instead of string[]. There is an overload of WriteAllLines that uses string[], but there is also one that is IEnumerable<string>, so any collection type can be used, not just string[].

Page 307: Correction: The keyword for case guards is when not where.

The speedrun states that a case guard is made by using the where keyword, but the keyword to use is when. Therefore, the final bullet point in the speedrun should actually read:

Switches also have cause guards, using the when keyword: Snake s when s.Length > 2.

Page 312: Correction: Snake length <= 5 instead of < 5

The second code snippet on this page looks like this:

Snake { Length: < 2 }          => 1,
Snake { Length: >= 2 and < 5 } => 3,
Snake { Length: > 5 }          => 7,

This version of the code does not correctly account for a snake of length 5, which wouldn’t fit into either the 3-point or 7-point category. The text above this snippet makes it clear that a snake of length 5 should fit into the 3-point category, and therefore, this snippet would more correctly be written as:

Snake { Length: < 2 }           => 1,
Snake { Length: >= 2 and <= 5 } => 3,
Snake { Length: > 5 }           => 7,

Page 316: Correction: Speedrun operator overload uses wrong variable name

In the speedrun at the start of the Operator Overloading level, the first line includes the following code:

public static Point operator +(Point p1, Point p2) => new Point(p1.X + vp.X, p1.Y + p2.Y);

This is an error because there is no vp variable. Instead, this should refer to p2:

public static Point operator +(Point p1, Point p2) => new Point(p1.X + p2.X, p1.Y + p2.Y);

Page 319: Simplification: Defining “indices” a second time.

On page 319, in the second paragraph, the book defines the word “indices.” However that word was also defined earlier on in the book, and this is a second definition that isn’t strictly necessary.

Page 320: Typo: “Convert between these two types…” should be “Converting between these two types…”

About half way down the page, there is a paragraph that starts with “Convert between these two types…” which isn’t grammatically correct. It should read, “Converting between these two types…”

Page 321: Clarification: Custom conversions can cause problems for both value and reference types

The first sentence in the subsection “The Pitfalls of Custom Conversions and Some Alternatives” states that custom conversions create new objects for reference types, which can cause problems.

In fact, custom conversions create new objects and values for both reference and value types, though the consequences of this are more unexpected with reference types than value types.

If you look at the example provided at the start of that section, if Point2 and Point3 were structs instead, you would already be assuming that the point is copied in full to the MoveLeft method– that it is already a separate copy of the data that MoveLeft is working with.

Thus, it is a slight improvement to state that, “Custom conversions create new objects, which can have unexpected consequences for reference types.” (Though if you don’t understand how value types are passed to methods or think some type is a reference type instead of a value type, you’d make the same mental mistake, even without the conversion.)

Page 326: Minor Improvement: Extra Space

In the final code snippet on this page, on the last line, there is an extra space after the o. in select (o, $"{o. HP}/{o.MaxHP}"); that can be removed to make the code cleaner.

Page 327: Minor Improvement: Extra Space

In the first code snippet on this page, on its second line, there is an extra space after the o. in where o. HP > 0 that can be removed to make the code cleaner.

Page 327: Correction: Ordering of HP

In several of the examples on this page, the names of the variables implies that ordering is automatically done in descending order, which is not true.

For example, the first code block in the Ordering section is:

var strongestObjects = from o in Objects
                       orderby o.MaxHP
                       select o;

But because this orders in ascending order (1, 2, 3, …) this is actually the weakest objects, and the following is more accurate:

The second code block in that section has a similar problem, but reversed, as does the third one.

Page 328: Correction: Top Three

The second to last code block on this page has a line that says:

var topThree = objects.OrderBy(m => m.HP).Take(3);

The variable name is misleading there. Given that OrderBy is in ascending order, it begins with lower values, not higher values. A better variable name would be var firstThree.

Page 328: Minor Improvement: Extra Space

In the second code snippet on this page, on its final line, there is an extra space after the o. in .Select(o => o. HP / o.MaxHP); that can be removed to make the code cleaner.

Page 330: Typo: “…each range variable use in…” to “…each range variable to use in…”

The first paragraph on page 330, after the code snippet, on the second line, states, “After the on, you can specify which part of each range varaible use in determining a pairing.” This is not grammatically correct. It should read, “After the on, you can specify which part of each range variable to use in determining a pairing.”

Page 332: Minor Improvement: Extra Space

In the first code snippet on this page, on its second line, there is an extra space after the o. in where o. HP > 0 that can be removed to make the code cleaner.

Page 338 through 340: Clarification: Public or private Number field

If you’ve been reading the book up to this point, you know that I have often made a strong argument for keeping fields private. Yet the example on page 338 has a public Number field in the SharedData class.

I had originally decided to go that route to make it clear that this was about direct variable access, not anything specific to properties. There’s a threat any time you write to a memory location from multiple threads. Making Number a field instead of a property makes that clearer, and making Number be public was a minor convenience to make it easy to use it (the typical benefits of a property).

For such a small program, there is no real consequence to a public field. But it does go against the advice I’ve given throughout the book, and it would be better to reconcile that. For example, making the field be private int _number, and then having a public int Number => _number; would do the trick without making it much more complicated. Then throughout the code blocks in the Thread Safety section, it would generally refer to _number instead of Number, with the exception of outside of the class.

Pages 339 and 340: Correction: _numberLock vs. numberLock

The code snippet that starts on page 339 and ends on page 340, as well as the paragraph following it, declares a variable called _numberLock, but then subsequently uses and refers to it as numberLock without the leading underscore. Either can be a valid variable name, but the main issue here is that it is inconsistent, making this code not compile as-is. Fields like _numberLock start with an underscore by convention, so the lock statement should read lock (_numberLock), and the paragraph on page 340 should refer to it as lock (_numberLock) as well.

Page 345: Typo: Missing Semicolon

The first code block on this page has a line with a missing semicolon:

Console.WriteLine(result)

For this sample to compile, it needs a trailing semicolon on that line.

Page 351: Correction: Speedrun definition of dynamic

The definition of dynamic in the speed run is confusing and wrong: “If you want the compiler not to defer checking the type and usage of a variable until run time, use the dynamic type.”

A better phrasing would be: “The dynamic type instructs the compiler not to check a variable’s type. It is checked while running instead.”

Page 352: Typo: “…whichever object mystery contains as the time the operation is used.”

This is a simple typo that should be “at”: “…whichever object mystery contains at the time the operation is used.”

Page 359: Clarification: “This puts the address of x…”

In the comment in the first code block on the page, it says: “This puts the address of ‘x’ and puts it in ‘pointerToX’.”

This would be better phrased as: “This gets the address of ‘x’ and puts it in ‘pointerToX’.”

Page 372: Correction: Error in Bit Pattern

In the first paragraph on this page is a sentence stating, “shiftedRight will contain the bit pattern with all bits moved three spots to the right. 001001 becomes 00000100.” The first bit pattern is missing a bit, and the sentence should be: “00010001 becomes 000000100”.

Page 372: Typo: “This coded ended up using int.”

The last paragraph before the subsection “Bitwise Logical Operators” states, “this coded ended up using int.” That is a typo and should read, “this code ended up using int.”

Submit a Problem

If you think you may have found an error (large or small) in the C# Player’s Guide, please let me know about it so that I can get it fixed.

One great approach is to submit an issue or discussion on the book’s GitHub page.

Other ways to reach out are found on the contact page, and they will all get the job done as well.