📄 Page
1
C# 12 Pocket Reference Instant Help for C# 12 Programmers Joseph Albahari & Ben Albahari
📄 Page
2
C# 12 Pocket Reference Looking for quick answers for using C# 12? This tightly focused and practical guide tells you exactly what you need to know without long intros or bloated samples. Succinct and easy to browse, this pocket reference is an ideal quick source of information. If you know Java, C++, or an earlier C# version, this guide will help you get rapidly up to speed. All programs and code snippets are available as interactive samples in LINQPad. You can edit these samples and instantly see the results without needing to set up projects in Visual Studio. Written by the authors of C# 12 in a Nutshell, this pocket reference covers: • C# fundamentals and features new to C# 12 • Advanced topics like operator overloading, type constraints, nullable types, operator lifting, closures, patterns, and asynchronous functions • LINQ sequences, lazy execution, standard query operators, and query expressions • Unsafe code and pointers, custom attributes, preprocessor directives, and XML documentation C# 9 7 8 1 0 9 8 1 4 7 5 4 9 5 2 4 9 9 US $24.99 CAN $31.99 ISBN: 978-1-098-14754-9 Twitter: @oreillymedia linkedin.com/company/oreilly-media youtube.com/oreillymedia
📄 Page
3
978-1-098-14754-9 [LSI] C# 12 Pocket Reference by Joseph Albahari and Ben Albahari Copyright © 2024 Joseph Albahari and Ben Albahari. All rights reserved. Printed in the United States of America. Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebasto‐ pol, CA 95472. O’Reilly books may be purchased for educational, business, or sales pro‐ motional use. Online editions are also available for most titles (https:// oreilly.com). For more information, contact our corporate/institutional sales department: 800-998-9938 or corporate@oreilly.com. Acquisitions Editor: Brian Guerin Development Editor: Sarah Grey Production Editor: Kristen Brown Copyeditor: Charles Roumeliotis Proofreader: Piper Editorial Consulting, LLC Indexer: WordCo Indexing Services, Inc. Interior Designer: David Futato Cover Designer: Karen Montgomery Illustrator: Kate Dullea November 2023: First Edition Revision History for the First Edition 2023-10-27: First Release See https://oreil.ly/c12prERR for release details. The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. C# 12 Pocket Reference, the cover image, and related trade dress are trademarks of O’Reilly Media, Inc. The views expressed in this work are those of the authors and do not repre‐ sent the publisher’s views. While the publisher and the authors have used good faith efforts to ensure that the information and instructions contained in this work are accurate, the publisher and the authors disclaim all respon‐ sibility for errors or omissions, including without limitation responsibility for damages resulting from the use of or reliance on this work. Use of the information and instructions contained in this work is at your own risk. If any code samples or other technology this work contains or describes is subject to open source licenses or the intellectual property rights of others, it is your responsibility to ensure that your use thereof complies with such licenses and/or rights.
📄 Page
4
Table of Contents C# 12 Pocket Reference 1 A First C# Program 2 Syntax 5 Type Basics 8 Numeric Types 19 Boolean Type and Operators 26 Strings and Characters 28 Arrays 34 Variables and Parameters 40 Expressions and Operators 50 Null Operators 56 Statements 58 Namespaces 68 Classes 73 Inheritance 95 The object Type 106 Structs 110 Access Modifiers 113 Interfaces 115 iii
📄 Page
5
Enums 121 Nested Types 124 Generics 124 Delegates 134 Events 141 Lambda Expressions 146 Anonymous Methods 152 try Statements and Exceptions 153 Enumeration and Iterators 161 Nullable Value Types 167 Nullable Reference Types 172 Extension Methods 174 Anonymous Types 176 Tuples 177 Records 179 Patterns 187 LINQ 192 Dynamic Binding 218 Operator Overloading 226 Attributes 229 Caller Info Attributes 233 Asynchronous Functions 235 Static Polymorphism 247 Unsafe Code and Pointers 250 Preprocessor Directives 255 XML Documentation 258 Index 263 iv | Table of Contents
📄 Page
6
C# 12 Pocket Reference C# is a general-purpose, type-safe, primarily object-oriented programming language, the goal of which is programmer pro‐ ductivity. To this end, the language balances simplicity, expres‐ siveness, and performance. C# 12 is designed to work with the Microsoft .NET 8 runtime (whereas C# 11 targets .NET 7, C# 10 targets .NET 6, and C# 7 targets Microsoft .NET Framework 4.6/4.7/4.8). NOTE The programs and code snippets in this book mirror those in Chapters 2 through 4 of C# 12 in a Nutshell and are all available as interactive samples in LINQPad. Work‐ ing through these samples in conjunction with the book accelerates learning in that you can edit the samples and instantly see the results without needing to set up projects and solutions in Visual Studio. To download the samples, click the Samples tab in LINQ‐ Pad and then click “Download more samples.” LINQPad is free—go to www.linqpad.net. 1
📄 Page
7
A First C# Program Following is a program that multiplies 12 by 30 and prints the result, 360, to the screen. The double forward slash indicates that the remainder of a line is a comment: int x = 12 * 30; // Statement 1 System.Console.WriteLine (x); // Statement 2 Our program consists of two statements. Statements in C# exe‐ cute sequentially and are terminated by a semicolon. The first statement computes the expression 12 * 30 and stores the result in a variable, named x, whose type is a 32-bit integer (int). The second statement calls the WriteLine method on a class called Console, which is defined in a namespace called System. This prints the variable x to a text window on the screen. A method performs a function; a class groups function mem‐ bers and data members to form an object-oriented build‐ ing block. The Console class groups members that handle command-line input/output (I/O) functionality, such as the WriteLine method. A class is a kind of type, which we examine in “Type Basics” on page 8. At the outermost level, types are organized into namespaces. Many commonly used types—including the Console class— reside in the System namespace. The .NET libraries are organ‐ ized into nested namespaces. For example, the System.Text namespace contains types for handling text, and System.IO contains types for input/output. Qualifying the Console class with the System namespace on every use adds clutter. The using directive lets you avoid this clutter by importing a namespace: using System; // Import the System namespace int x = 12 * 30; Console.WriteLine (x); // No need to specify System 2 | C# 12 Pocket Reference
📄 Page
8
A basic form of code reuse is to write higher-level functions that call lower-level functions. We can refactor our program with a reusable method called FeetToInches that multiplies an integer by 12, as follows: using System; Console.WriteLine (FeetToInches (30)); // 360 Console.WriteLine (FeetToInches (100)); // 1200 int FeetToInches (int feet) { int inches = feet * 12; return inches; } Our method contains a series of statements surrounded by a pair of braces. This is called a statement block. A method can receive input data from the caller by specifying parameters and output data back to the caller by specifying a return type. Our FeetToInches method has a parameter for inputting feet, and a return type for outputting inches: int FeetToInches (int feet) ... The literals 30 and 100 are the arguments passed to the Feet ToInches method. If a method doesn’t receive input, use empty parentheses. If it doesn’t return anything, use the void keyword: using System; SayHello(); void SayHello() { Console.WriteLine ("Hello, world"); } Methods are one of several kinds of functions in C#. Another kind of function we used in our example program was the * operator, which performs multiplication. There are also con‐ structors, properties, events, indexers, and finalizers. A First C# Program | 3
📄 Page
9
Compilation The C# compiler compiles source code (a set of files with the .cs extension) into an assembly. An assembly is the unit of pack‐ aging and deployment in .NET. An assembly can be either an application or a library. A normal console or Windows applica‐ tion has an entry point, whereas a library does not. The purpose of a library is to be called upon (referenced) by an application or by other libraries. .NET itself is a set of libraries (as well as a runtime environment). Each of the programs in the preceding section began directly with a series of statements (called top-level statements). The presence of top-level statements implicitly creates an entry point for a console or Windows application. (Without top- level statements, a Main method denotes an application’s entry point—see “Symmetry of predefined types and custom types” on page 10.) To invoke the compiler, you can either use an integrated devel‐ opment environment (IDE) such as Visual Studio or Visual Studio Code, or call it manually from the command line. To manually compile a console application with .NET, first down‐ load the .NET 8 SDK, and then create a new project, as follows: dotnet new console -o MyFirstProgram cd MyFirstProgram This creates a folder called MyFirstProgram, which contains a C# file called Program.cs, which you can then edit. To invoke the compiler, call dotnet build (or dotnet run, which will compile and then run the program). The output will be writ‐ ten to a subdirectory under bin\debug, which will include MyFirstProgram.dll (the output assembly) as well as MyFirst‐ Program.exe (which runs the compiled program directly). 4 | C# 12 Pocket Reference
📄 Page
10
Syntax C# syntax is inspired by C and C++ syntax. In this section, we describe C#’s elements of syntax, using the following program: using System; int x = 12 * 30; Console.WriteLine (x); Identifiers and Keywords Identifiers are names that programmers choose for their classes, methods, variables, and so on. Here are the identifiers in our example program, in the order in which they appear: System x Console WriteLine An identifier must be a whole word, essentially made up of Unicode characters starting with a letter or underscore. C# identifiers are case sensitive. By convention, parameters, local variables, and private fields should be in camel case (e.g., myVari able), and all other identifiers should be in Pascal case (e.g., MyMethod). Keywords are names that mean something special to the com‐ piler. There are two keywords in our example program, using and int. Most keywords are reserved, which means that you can’t use them as identifiers. Here is the full list of C# reserved keywords: Syntax | 5
📄 Page
11
abstract as base bool break byte case catch char checked class const continue decimal default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long namespace new null object operator out override params private protected public readonly record ref return sbyte sealed short sizeof stackalloc static string struct switch this throw true try typeof uint ulong unchecked unsafe ushort using virtual void volatile while Avoiding conflicts If you really want to use an identifier that clashes with a reserved keyword, you can do so by qualifying it with the @ prefix. For instance: class class {...} // Illegal class @class {...} // Legal The @ symbol doesn’t form part of the identifier itself. So @myVariable is the same as myVariable. Contextual keywords Some keywords are contextual, meaning they can also be used as identifiers—without an @ symbol. The contextual keywords are as follows: 6 | C# 12 Pocket Reference
📄 Page
12
add alias and ascending async await by descending dynamic equals file from get global group init into join let managed nameof nint not notnull nuint on or orderby partial remove required select set unmanaged value var with when where yield With contextual keywords, ambiguity cannot arise within the context in which they are used. Literals, Punctuators, and Operators Literals are primitive pieces of data lexically embedded into the program. The literals we used in our example program are 12 and 30. Punctuators help demarcate the structure of the program. An example is the semicolon, which terminates a statement. Statements can wrap multiple lines: Console.WriteLine (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10); An operator transforms and combines expressions. Most oper‐ ators in C# are denoted with a symbol, such as the multiplica‐ tion operator, *. Here are the operators in our program: = * . () A period denotes a member of something (or a decimal point with numeric literals). Parentheses are used when declaring or calling a method; empty parentheses are used when the method accepts no arguments. The equals sign performs assignment (the double equals sign, ==, performs equality comparison). Comments C# offers two different styles of source code documentation: single-line comments and multiline comments. A single-line Syntax | 7
📄 Page
13
comment begins with a double forward slash and continues until the end of the line. For example: int x = 3; // Comment about assigning 3 to x A multiline comment begins with /* and ends with */. For example: int x = 3; /* This is a comment that spans two lines */ Comments can embed XML documentation tags (see “XML Documentation” on page 258). Type Basics A type defines the blueprint for a value. In our example, we used two literals of type int with values 12 and 30. We also declared a variable of type int whose name was x. A variable denotes a storage location that can contain different values over time. In contrast, a constant always represents the same value (more on this later). All values in C# are an instance of a specific type. The meaning of a value, and the set of possible values a variable can have, is determined by its type. Predefined Type Examples Predefined types (also called built-in types) are types that are specially supported by the compiler. The int type is a prede‐ fined type for representing the set of integers that fit into 32 bits of memory, from −231 to 231−1. We can perform functions such as arithmetic with instances of the int type as follows: int x = 12 * 30; Another predefined C# type is string. The string type represents a sequence of characters, such as “.NET” or “http:// oreilly.com. We can work with strings by calling functions on them, as follows: 8 | C# 12 Pocket Reference
📄 Page
14
string message = "Hello world"; string upperMessage = message.ToUpper(); Console.WriteLine (upperMessage); // HELLO WORLD int x = 2022; message = message + x.ToString(); Console.WriteLine (message); // Hello world2022 The predefined bool type has exactly two possible values: true and false. The bool type is commonly used to conditionally branch execution flow with an if statement. For example: bool simpleVar = false; if (simpleVar) Console.WriteLine ("This will not print"); int x = 5000; bool lessThanAMile = x < 5280; if (lessThanAMile) Console.WriteLine ("This will print"); The System namespace in .NET contains many important types that are not predefined by C# (e.g., DateTime). Custom Type Examples Just as you can build complex functions from simple functions, you can build complex types from primitive types. In this example, we will define a custom type named UnitConverter— a class that serves as a blueprint for unit conversions: UnitConverter feetToInches = new UnitConverter (12); UnitConverter milesToFeet = new UnitConverter (5280); Console.WriteLine (feetToInches.Convert(30)); // 360 Console.WriteLine (feetToInches.Convert(100)); // 1200 Console.WriteLine (feetToInches.Convert (milesToFeet.Convert(1))); // 63360 public class UnitConverter { int ratio; // Field public UnitConverter (int unitRatio) // Constructor Type Basics | 9
📄 Page
15
{ ratio = unitRatio; } public int Convert (int unit) // Method { return unit * ratio; } } Members of a type A type contains data members and function members. The data member of UnitConverter is the field called ratio. The function members of UnitConverter are the Convert method and the UnitConverter’s constructor. Symmetry of predefined types and custom types A beautiful aspect of C# is that predefined types and custom types have few differences. The predefined int type serves as a blueprint for integers. It holds data—32 bits—and provides function members that use that data, such as ToString. Simi‐ larly, our custom UnitConverter type acts as a blueprint for unit conversions. It holds data—the ratio—and provides func‐ tion members to use that data. Constructors and instantiation Data is created by instantiating a type. You can instantiate pre‐ defined types simply by using a literal such as 12 or "Hello world". The new operator creates instances of a custom type. We started our program by creating two instances of the UnitConverter type. Immediately after the new operator instantiates an object, the object’s constructor is called to perform initialization. A constructor is defined like a method, except that the method name and return type are reduced to the name of the enclosing type: 10 | C# 12 Pocket Reference
📄 Page
16
public UnitConverter (int unitRatio) // Constructor { ratio = unitRatio; } Instance versus static members The data members and function members that operate on the instance of the type are called instance members. UnitConverter’s Convert method and int’s ToString method are examples of instance members. By default, members are instance members. Data members and function members that don’t operate on the instance of the type can be marked as static. To refer to a static member from outside its type, you specify its type name rather than an instance. An example is the WriteLine method of the Console class. Because this is static, we call Console.Write Line() and not new Console().WriteLine(). In the following code, the instance field Name pertains to an instance of a particular Panda, whereas Population pertains to the set of all Panda instances. We create two instances of the Panda, print their names, and then print the total population: Panda p1 = new Panda ("Pan Dee"); Panda p2 = new Panda ("Pan Dah"); Console.WriteLine (p1.Name); // Pan Dee Console.WriteLine (p2.Name); // Pan Dah Console.WriteLine (Panda.Population); // 2 public class Panda { public string Name; // Instance field public static int Population; // Static field public Panda (string n) // Constructor { Name = n; // Instance field Population = Population + 1; // Static field } } Type Basics | 11
📄 Page
17
Attempting to evaluate p1.Population or Panda.Name will gen‐ erate a compile-time error. The public keyword The public keyword exposes members to other classes. In this example, if the Name field in Panda was not marked as public, it would be private and could not be accessed from outside the class. Marking a member public is how a type communicates: “Here is what I want other types to see—everything else is my own private implementation details.” In object-oriented terms, we say that the public members encapsulate the private mem‐ bers of the class. Creating a namespace Particularly with larger programs, it makes sense to organize types into namespaces. Here’s how to define the Panda class inside a namespace called Animals: namespace Animals { public class Panda { ... } } We cover namespaces in detail in “Namespaces” on page 68. Defining a Main method All of our examples so far have used top-level statements, a fea‐ ture that was introduced in C# 9. Without top-level statements, a simple console or Windows application looks like this: using System; class Program { static void Main() // Program entry point { int x = 12 * 30; Console.WriteLine (x); 12 | C# 12 Pocket Reference
📄 Page
18
} } In the absence of top-level statements, C# looks for a static method called Main, which becomes the entry point. The Main method can be defined inside any class (and only one Main method can exist). The Main method can optionally return an integer (rather than void) in order to return a value to the execution environment (where a nonzero value typically indicates an error). The Main method can also optionally accept an array of strings as a parameter (that will be populated with any arguments passed to the executable); for example: static int Main (string[] args) {...} NOTE An array (such as string[]) represents a fixed number of elements of a particular type. Arrays are specified by placing square brackets after the element type. We describe them in “Arrays” on page 34. (The Main method can also be declared async and return a Task or Task<int> in support of asynchronous programming—see “Asynchronous Functions” on page 235.) Top-level statements Top-level statements let you avoid the baggage of a static Main method and a containing class. A file with top-level statements comprises three parts, in this order: Type Basics | 13
📄 Page
19
1. (Optionally) using directives 2. A series of statements, optionally mixed with method declarations 3. (Optionally) Type and namespace declarations Everything in Part 2 ends up inside a compiler-generated “main” method, inside a compiler-generated class. This means that the methods in your top-level statements become local methods (we describe the subtleties in “Local methods” on page 75). Top-level statements can optionally return an integer value to the caller, and access a “magic” variable of type string[] called args, corresponding to command-line arguments passed by the caller. As a program can have only one entry point, there can be at most one file with top-level statements in a C# project. Types and Conversions C# can convert between instances of compatible types. A conversion always creates a new value from an existing one. Conversions can be either implicit or explicit: implicit con‐ versions happen automatically, whereas explicit conversions require a cast. In the following example, we implicitly convert an int to a long type (which has twice the bit capacity of an int) and explicitly cast an int to a short type (which has half the bit capacity of an int): int x = 12345; // int is a 32-bit integer long y = x; // Implicit conversion to 64-bit int short z = (short)x; // Explicit conversion to 16-bit int In general, implicit conversions are allowed when the compiler can guarantee that they will always succeed without loss of information. Otherwise, you must perform an explicit cast to convert between compatible types. 14 | C# 12 Pocket Reference
📄 Page
20
Value Types Versus Reference Types C# types can be divided into value types and reference types. Value types comprise most built-in types (specifically, all numeric types, the char type, and the bool type) as well as custom struct and enum types. Reference types comprise all class, array, delegate, and interface types. The fundamental difference between value types and reference types is how they are handled in memory. Value types The content of a value type variable or constant is simply a value. For example, the content of the built-in value type int is 32 bits of data. You can define a custom value type with the struct keyword (see Figure 1): public struct Point { public int X, Y; } Figure 1. A value type instance in memory The assignment of a value type instance always copies the instance. For example: Point p1 = new Point(); p1.X = 7; Point p2 = p1; // Assignment causes copy Console.WriteLine (p1.X); // 7 Console.WriteLine (p2.X); // 7 p1.X = 9; // Change p1.X Console.WriteLine (p1.X); // 9 Console.WriteLine (p2.X); // 7 Type Basics | 15