Errata

C# 9.0 in a Nutshell

Errata for C# 9.0 in a Nutshell

Submit your own errata for this product.

The errata list is a list of errors and their corrections that were found after the product was released.

The following errata were submitted by our customers and have not yet been approved or disproved by the author or editor. They solely represent the opinion of the customer.

Color Key: Serious technical mistake Minor technical mistake Language or formatting error Typo Question Note Update

Version Location Description Submitted by Date submitted
PDF Page 9
Last paragraph

The last code presented in the page is not equivalent to the previous code because of the change of the coordinate's types from double to int. The correct must be:

record Point (double X, double Y);

instead of:

record Point(int X, int Y);

Rogério Moraes de Carvalho  May 08, 2021 
ePub Page 11
Chapter 1 Platform Support

Platform support for macOS stated: web and command line applications. However, VS for Mac creates rich-client applications just fine using either Xamarin or GTK#. This looks like a minor technical error, but it may have huge impact on a person assessing C#.

Andrei  Mar 03, 2021 
PDF Page 29
Contextual keywords topic

The list of contextual keywords was not updated with the new C# 9 contextual keywords. The following new contextual keywords are missing:
init
managed
nint
notnull
nuint

Another mistake is that the keyword "in" is classified as contextual keyword, but it is a keyword. In the book, the "in" keyword appears in both lists: keywords, and contextual keywords.

Rogério Moraes de Carvalho  May 12, 2021 
PDF Page 33
Paragraph at "The public keyword" topic

The explanation cite a "Test" class that doesn't exist anymore, like in the previous version of the book: "C# 8.0 in a Nutshell". This is because the author moved the code inside the "Main" static method in the "Test" class to outside using the new top-level statements introduced in C# 9.0.

Rogério Moraes de Carvalho  May 12, 2021 
PDF Page 35
Top-Level Statements (C# 9)

"Because the CLR doesn't explicitly support top-level statements, the compiler translates your code into something like this:

using System;

static class Program$ // Special compiler-generated name
{
static void Main$() // Soecial compiler-generated name
{
...

With tools like .NET assembly browser and decompiler "ILSpy 7.0+", the author can view exactly how the compiler translates the code as follow:

using System;
using System.Runtime.CompilerServices;

[CompilerGenerated]
internal static class <Program>$
{
private static void <Main>$(string[] args)
{
...

It is important to note that the array of strings "args" is declared as a parameter in the "Main" method by the compiler and can be accessed in top-level statements.

Rogério Moraes de Carvalho  May 12, 2021 
PDF Page 41
Table 2-1. Predefined numeric types in C#

The first data row of the table, for integral-signed numeric types, shows "sbyte" as the "System type" of the "sbyte" C# type. The correct must be "SByte", once the type use pascal case and C# is case-sensitive.

Rogério Moraes de Carvalho  May 13, 2021 
PDF Page 41
Footnote 2

There is an inconsistence between the "C# 6.0 draft specification" (last spec about features until C# 6.0) and the "System.Decimal" API documentation. The API documentation of the "System.Decimal" struct starts with the following text: "Represents a decimal floating-point number".

In the "Types" topic of the "C# 6.0 draft specification", it doesn't classify 'decimal' as a "floating_point_type" with 'float' and 'double' data types, It is classified as an special case of 'numeric_type'.

Rogério Moraes de Carvalho  May 13, 2021 
PDF Page 43
The last but one code example before the topic "Numeric Conversions"

Follow the text fragment with a mistake in the code:
"Without the F suffix, the following line would not compile, because 4.5 would be inferred to be of type double, which has no implicit conversion to float:

float f = 4.5F;"

The code is correct and will compile successfully. According to text description, the wrong code must be:

float f = 4.5;

In this case, the literal 4.5 will be of the type "double" and cannot be implicitly converted to float, causing the following compile-time error:
"error CS0664: Literal of type double cannot be implicitly converted to type 'float'; use an 'F' suffix to create a literal of this type"

Rogério Moraes de Carvalho  May 13, 2021 
PDF Page 45
First paragraph of the "Division" topic

The following text fragment contains a mathematical mistake:
"Division operations on integral types always truncate remainders (round toward zero)."

The correct must be:
A division operation on integral types truncate the quotient.

Rogério Moraes de Carvalho  May 13, 2021 
PDF Page 47
First paragraph of the "NativepSized Integers (C# 9)" topic

Follow the beginning of the first paragraph: "The nint and unint native-sized integers types...".

There is a simple typo for the native unsigned integer type that must be "nuint" instead of "unint", where the first and second characters where changed in the first paragraph.

Rogério Moraes de Carvalho  May 14, 2021 
PDF Page 47
The last code of the page on the topic "Native-Sized Integers (C# 9)"

The following text and code are not 100% precise:
"You cam implicitly convert between them:

nint x = 123;
IntPtr p = x;
nint y = p;"

As the book author explains, the types nint and nuint are represented by underlying types System.IntPtr and System.UIntPtr, respectively, but with compiler surfacing additional conversions and operations for those types as native ints.

In the subtopic "Conversions" of the topic "Native-sized integers" of the "C# 9.0 specification proposal", we have the following explanation:
"There is an identity conversion between nint and IntPtr, and between nuint and UIntPtr."

Notice the use of "identity conversion" instead of "implicit conversion", because this is a very special case supported by the compiler. Implicit conversion is not appropriated because there is no implicit operator implementation to support implicit conversion.

Rogério Moraes de Carvalho  May 14, 2021 
ePub Page 50
List of keywords and Contextual keywords

1. The following contextual keywords are missing from the list:
init.nint,nuint,notnull.
2. The in keyword appears both as a keyword and as a contextul keyword.
3. C# official web page lists the record keyword as a regular keyword, not as a contextual keyword.

ariel  Mar 30, 2021 
PDF Page 50
Contextual keywords topic

The list of contextual keywords was not updated with the new C# 9 contextual keywords. The following new contextual keywords are missing:
init
managed
nint
notnull
nuint

Another mistake is that the keyword "in" is classified as contextual keyword, but it is a normal keyword.




Rogério Moraes de Carvalho  May 12, 2021 
PDF Page 54
Last code of the "String interpolation" topic

The following last code of the topic "String interpolation" will not behave like desired:

int x = 2;
// Note that $ must appear before @ prior to C# 8:
string s = $@"this spans {
x} lines";

The CR-LF character was positioned inside the braces that delimit the interpolation expression, in this case: the x value. Once the interpolation expression inside the braces is evaluated to 2, the CR-LF is lost and the s string will contain a string with only one line.

To correct the code, you must put the CR-LF outside the braces, as the following example possible code:

int x = 2;
// Note that $ must appear before @ prior to C# 8:
string s = $@"this spans
{x} lines";

Now, the code produces the following string:
"this spans
2 lines"

Rogério Moraes de Carvalho  May 14, 2021 
PDF Page 57
First paragraph of the "Multidimensional Arrays" topic

According to the "C# programming guide" in the official Microsoft documentation: "An array can be single-dimensional, multidimensional or jagged".

In the beginning of the first paragraph of the topic "Multidimensional Arrays", the author explains:
"Multidimensional arrays come in two varieties: rectangular and jagged."

Following the paragraph, the author introduces a subtopic "Rectangular arrays" as a subdivision of multidimensional arrays. The name rectangular array is only appropriate for a two-dimensional array, that is represented by elements disposed in rows and columns. The name is not appropriated for three-dimensional arrays, four-dimensional arrays, and so on.

A jagged array isn't required to be a multidimensional array, but an array of arrays. A jagged array can be a single-dimensional array or a multi-dimensional array in which its elements are other single-dimensional or multidimensional arrays, and so on.
For example, the following code is a declaration of a single-dimensional array of characters that has five elements, each of which is a single-dimensional array of characters:

char[][] charJaggedArray = new char[5][];

The above code is an example of a jagged array composed only by single-dimensional arrays, then it isn't correct to classify jagged arrays as multidimensional arrays.

Rogério Moraes de Carvalho  May 14, 2021 
PDF Page 61
Table on "Default Values" topic

The table of default values omit the following two categories of types:

Type: All nullable value types
Default value: null (the nullable value type instance will have the "HasValue" property with value false, and the attempt to read the "Value" property will throw the "InvalidOperationException" with the message: "Nullable object must have a value.")

Type: struct
Default value: The value produced by setting all struct fields to their default values.

Rogério Moraes de Carvalho  May 16, 2021 
PDF Page 62
Table of parameter modifiers on "Parameters" topic

Following the authors table of parameter modifiers, the table can be modified for more accuracy:
-----------------------------------------------------------------------------------------------
Modifier | Passed by | Variable must be definitely assigned
-----------------------------------------------------------------------------------------------
(None) | Value | Going in
ref | Reference | Going in and out
in | Reference (read-only) | Going in
out | Reference (write-only) | Going out
-----------------------------------------------------------------------------------------------

Rogério Moraes de Carvalho  May 16, 2021 
PDF Page 64
Code on the "The out modifier" topic

It's a good idea to show a simplified version of the code too using a new feature starting with C# 7.0: the possibility of declaring out variables in the argument list of the method call, rather than in a separate variable declaration.

Original code:
string a, b;
Split("Steve Ray Vaughn", out a, out b);
...

Simplified code (feature starting with C# 7.0):
Split("Steve Ray Vaughn", out string a, out string b);
...

Rogério Moraes de Carvalho  May 16, 2021 
PDF Page 66
First paragraph of the topic "The params modifier"

The book's author inform that "The parameter type must be declared as an array, as shown in the following example: ...".

But, it's important that he clarifies: The parameter type must be declared as a single-dimensional array. If you try to use a multidimensional array as params parameter type, you will get the following compile-time error:
"error CS0225: The params parameter must be a single dimensional array"

Another aspect that must be clarified is that when you call a method with a params parameter, you can pass in no arguments in params position. In this case, the length of the params single-dimensional array will be zero.

Rogério Moraes de Carvalho  May 16, 2021 
PDF Page 66
Last but one paragraph of the page 66 on "Optional parameters" topic

Author's book instructions on the last but one paragraph: "The default value of an optional parameter must be specified by a constant expression or a parameterless constructor of a value type."

It exists one more possible case:
The parameter's default value can be an expression of the form default(AnyType), where AnyType can be either a value type or a reference type. If it's a reference type, it's the same as specifying null.

Rogério Moraes de Carvalho  May 16, 2021 
PDF Page 67
Last paragraph before the topic "Named arguments"

Author's book explanation:
"To do the converse (pass a default value to x and an explicit value to y), you must combine optional parameters with named arguments."

The use of named arguments is a possible solution, as the following code:
Foo(y: 1); // 0, 1

But, it isn't the only solution as the use of "must" suggests. For example, there is another possible solution with the use of the keyword "default", as the following code:
Foo(default, 1); // 0, 1

Rogério Moraes de Carvalho  May 16, 2021 
PDF Page 73
Third data row of "Operators Table": stackalloc

The operator name "Unsafe stack allocation" for "stackalloc" operator is now inappropriate. Starting with C# 7,2, you don't have to use an unsafe context when you assign a stack allocated memory block to a Span<T> or ReadOnlySpan<T> variable.

Rogério Moraes de Carvalho  May 18, 2021 
PDF Page 73
Primary postfix operator ! (null-forgiving) omission on "Operators Table"

Omission of the following operator in the "Table 2-3. C# operators (categories in order of precedence)":

Category | Operator symbol | Operator name | Example | User-overloadable
-----------------------------------------------------------------------------------------------------
Primary | ! (postfix) | Null-forgiving | x! | No

Rogério Moraes de Carvalho  May 18, 2021 
PDF Page 73
A middle row of the "Table 2-3. C# operators (categories in order of precedence)"

The "Table 2-3" lists C# operators categories in order of precedence. According to the "C# language reference", the "sizeof" operator is in the category "Primary" instead of "Unary", like the book's table. In this context, the book's table is classifying the "sizeof" operator with a lowest precedence in comparison with the "C# language reference". "sizeof" must be classified in the "Primary" category.

Rogério Moraes de Carvalho  May 18, 2021 
PDF Page 73
"Table 2-3. C# operators (categories in order of precedence)"

Omission of the following operators from the category "Unary" in the "Table 2-3. C# operators (categories in order of precedence)":

Category | Operator symbol | Operator name | Example | User-overloadable
-----------------------------------------------------------------------------------------------------
Unary | ^ | Index from end | array[^1] | No
| true and false | true and false | true | Yes

Note: The "index from end operator ^" is available in C# 8.0 and later.

Rogério Moraes de Carvalho  May 18, 2021 
PDF Page 73
"Table 2-3. C# operators (categories in order of precedence)"

Omission of the operators from the categories "switch expression", and "with expression" from the "Table 2-3. C# operators (categories in order of precedence)":

Category | Operator symbol | Operator name | Example | User-overloadable
-------------------------------------------------------------------------------------------------------
switch | switch | Switch | n switch { | No
expression | | | 0 => false, |
| | | _ => true, |
| | | } |
with | with | With | p with {X=2} | No
expression | | | |


Note: "switch expression" and "with expression" categories are bellow "Range" category, respectively, in the operator precedence. "switch expression" was introduced in C# 8.0, and "with expression" was introduced in C# 9.0.

Rogério Moraes de Carvalho  May 18, 2021 
PDF Page 73
Last but one row of the "Table 2-3. C# operators (categories in order of precedence)

The category "Relational" must be "Relational and type-testing" because the operators <, >, <=, and >= are relational, and the operators "is" and "as" are type-testing.

Rogério Moraes de Carvalho  May 18, 2021 
PDF Page 74
Category "Assignment and lambda" of the "Table 2-3. C# operators (categories in order of precedence)

Omission of the following operators from the category "Assignment and lambda" in the "Table 2-3. C# operators (categories in order of precedence)":

Category | Operator symbol | Operator name | Example | User-overloadable
-----------------------------------------------------------------------------------------------------
Assignment | %= | Remainder | r %= 2 | Via %
and lambda | | compound | |
| | operator | |

Rogério Moraes de Carvalho  May 18, 2021 
PDF Page 75
Topic "Null-Conditional Operator"

The topic explains only the null-conditional operator for member access (?.), but it's important to inform the null-conditional operator for element access (?[]). Both operators are available since C# 6.0.

Follow an example of both operator in the implementation of the local method "sum":

double sum(double[] numbers, int fromIndex, int toIndex) =>
numbers?[fromIndex..(toIndex + 1)]?.Sum() ?? double.NaN;

double[] numbers = { 1.0, 1.5, 2.0, 2.5, 3.0 };
Console.WriteLine(sum(numbers, 1, 3)); // 6

numbers = null;
Console.WriteLine(sum(numbers, 1, 3)); // NaN

Rogério Moraes de Carvalho  May 18, 2021 
PDF Page 76
First paragraph of the topic "Declaration Statements"

Follow a fragment of the first paragraph of the topic "Declaration Statements" on page 76 of the book:
"A declaration statement declares a new variable, optionally initializing the variable with an expression."

The explanation above is of "variable declaration", that is one of two possible declaration statements: variable declaration or constant declaration. The paragraph above must be corrected to:
"A variable declaration declares a new variable, optionally initializing the variable with an expression."

Rogério Moraes de Carvalho  May 18, 2021 
PDF Page 84
First paragraph of the topic "Jump Spatements"

The book's author classify "throw" as a jump statements with "break", "continue", "goto", and "return".

According to the "C# language reference", the "jump statements" are only: break, continue, goto, and return. throw is classified as one of the "Exception Handling Statements".

Rogério Moraes de Carvalho  May 19, 2021 
PDF Page 86
First paragraph of the topic "Miscellaneous Statements"

The book's author explains:
"The using statement provides an elegant syntax for calling Dispose on objects that implements IDisposible, within a finally block."

It's important to notice that starting with C# 8.0, the using statement extends the same elegant syntax for calling DisposeAsync on objects that implements IAsyncDisposable for releasing unmanaged resources asynchronously.

Rogério Moraes de Carvalho  May 20, 2021 
PDF Page 88
Note on topic "The using Directive"

The book's author ends the note with the following text:
"A good example is the TextBox class, which is defined both in System.Windows.Controls (WPF) and System.Windows.Forms.Controls (Windows Forms)."

The namespace System.Windows.Forms.Controls doesn't exist in the Windows Forms Framework. The correct namespace in Windows Forms that the TextBox class belongs to is System.Windows.Forms, without ending with Controls. The full qualified name of the TextBox class in Windows Forms is System.Windows.Forms.TextBox.

Rogério Moraes de Carvalho  May 20, 2021 
PDF Page 96
First paragraph of the "Field Initialization" topic

In the list of the most common possible default values inside parentheses, the char default value must be '\0', delimited by single quotes, instead of \0 without delimiters.

Rogério Moraes de Carvalho  May 20, 2021 
PDF Page 98
"Local methods" topic

"Local functions" is a more appropriate name than "local methods" because thery aren't associated with a class nor a struct. They are private methods of a type that are nested inside another member.

The feature is called "Local functions" in the "C# Programming Guide", and in "C# 7.0 specification proposal", where the feature was introduced.

Rogério Moraes de Carvalho  May 20, 2021 
PDF Page 106
The only note on the page

Follow the note of the page 106:
"Throughout the book, we use public fields extensively to keep the examples free of distraction. In a real application, you would typically favor public properties over public fields in order to promote encapsulation."

In my opinion, the author's must rethink the use of a such bad practice in object-oriented programming in favor of the use of auto-implemented properties, feature that is available in C# since version 3,0. The declaration syntax of auto-implemented properties is condensed almost like the declaration of fields, and promote encapsulation.

Rogério Moraes de Carvalho  May 24, 2021 
PDF Page 112
Topic "Static Classes"

The book's author explains "Static Classes" as follow:
"A class can be marked static. indicating that it must be composed solely of static members and cannot be subclassed."

The main concept is that: a static class cannot be instantiated, then you cannot use the new operator to create instances of a static class type. As a consequence, you cannot declare instance members, but only static members.

The fact that a static class cannot be subclassed is a consequence of the fact that it cannot be instantiated. A static class is implicitly sealed and abstract, but you cannot mark it with the modifiers 'sealed' or 'abstract'. There are other restrictions relative to the fact that a static class cannot be instantiated.

Rogério Moraes de Carvalho  May 24, 2021 
PDF Page 115
Last paragraph of the page 115

Follow the paragraph content:
"The derived classes, Stock and House, inherit the 'Name' property from the base class, Asset."

'In the example code, the member 'Name' is declared as a public field instead of a public property.

Rogério Moraes de Carvalho  May 24, 2021 
PDF Page 118
First note at the top of the page 118

The codes in approaches #1 and# 2 don't compile because the field 'SharesOwned' of the class 'Stock' is declared as 'long', and 'long' cannot be implicitly converted to "int". The code could be modified as follow:

long shares = ((Stock)a).SharesOwned; // Approach #1
long shares = (a as Stock).SharesOwned; // Approach #2

Rogério Moraes de Carvalho  May 24, 2021 
PDF Page 118
The first paragraph of page 118 after the first note.

Follow the paragraph content:
"The 'as' operator cannot perform custom conversions, and it cannot do numeric conversions:

long x = 3 as long; // Compile-time error"

It's important to inform to the book's readers that the 'as' operator must be used with a reference type or a nullable value type to convert the result of an expression to the target type or null if the conversion fails.

Rogério Moraes de Carvalho  May 24, 2021 
Printed Page 159
bottom code

I do not think the code at the bottom of the page can compile as such? If input as a top-level statement (as appears intended), it requires the 'Square' method to be declared as static.

Jean-Baptiste Mestelan  Feb 27, 2022 
PDF Page 170
3rd paragraph

public delegate void EventHandler<TEventArgs> (object source, TEventArgs e) where TEventArgs : EventArgs; This delegate has changed, there is no restriction on type TEventArgs, "where TEventArgs : EventArgs" has been removed.


Cao Yang  Aug 25, 2021 
PDF Page 274
7th Paragraph

Considering the statement "The Contains method doesn’t offer the convenience of this overload, although you
can achieve the same result with the IndexOf method." C# 10 does have this overload and this is possible => "abcdef".Contains("aBc", StringComparison.InvariantCultureIgnoreCase)

Muhammad Abdulmalik  Jan 05, 2023 
Printed Page 568
In the middle

You say in the WeakReference Delegate example that:

where TDelegate : Delegate //Compiler doesn't allow this


This is not true anymore. Since C# 7.3 type constraints can deal with Enum and Delegate constraints. I assume this also has influence in other parts of the book.

Arne Lüdtke  Dec 17, 2021 
PDF Page 847
First code listing

Not an error per se, but I guess this would be welcome change.
One line of the code says:

if (value is string) return (string) value;

I think that it would be better to use pattern matching:

if (value is string s) return s;

Eugene  May 25, 2021