(This page has no text content)
1 C# 10 Pocket Reference Instant Help for C# 10 Programmers Joseph Albahari and Ben Albahari
2 C# 10 Pocket Reference by Joseph Albahari and Ben Albahari Copyright © 2022 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, Sebastopol, CA 95472. O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles (http://oreilly.com). For more information, contact our corporate/institutional sales department: 800-998-9938 or corporate@oreilly.com. Acquisitions Editor: Amanda Quinn Development Editor: Corbin Collins 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 January 2022: First Edition Revision History for the First Edition 2022-01-18: First Release See https://oreil.ly/c10prERR for release details. e O’Reilly logo is a registered trademark of O’Reilly Media, Inc. C# 10 Pocket Reference, the cover image, and related trade dress are trademarks of O’Reilly Media, Inc. e views expressed in this work are those of the authors, and do not represent 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 responsibility 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. 978-1-098-12204-1 [LSI]
3 C# 10 Pocket Reference C# is a general-purpose, type-safe, primarily object-oriented programming language, the goal of which is programmer productivity. To this end, the language balances simplicity, expressiveness, and performance. C# 10 is designed to work with the Microso .NET 6 runtime (whereas C# 9 targets .NET 5, C# 8 targets .NET Core 3, and C# 7 targets .NET Core 2 and Microso .NET Framework 4.6/4.7/4.8). NOTE e programs and code snippets in this book mirror those in Chapters 2 through 4 of C# 10 in a Nutshell (O’Reilly) and are all available as interactive samples in LINQPad. Working 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 LINQPad and then click “Download more samples.” LINQPad is free—go to www.linqpad.net. A First C# Program Following is a program that multiplies 12 by 30 and prints the result, 360, to the screen. e 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# execute sequentially and are terminated by a semicolon. e first statement computes the expression 12 * 30 and stores the result in a variable, named x, whose type is a 32-bit integer (int). e second statement calls the WriteLine method on a class called Console, which is defined in a namespace called System. is prints the variable x to a text window on the screen. A method performs a function; a class groups function members and data members to form an object-oriented building block. e 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”. At the outermost level, types are organized into namespaces. Many commonly used types— including the Console class—reside in the System namespace. e .NET libraries are organized 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. e using directive lets you avoid this clutter by importing a namespace:
4 using System; // Import the System namespace int x = 12 * 30; Console.WriteLine (x); // No need to specify System 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. is 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) ... e literals 30 and 100 are the arguments passed to the FeetToInches 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. ere are also constructors, properties, events, indexers, and finalizers. Compilation e C# compiler compiles source code (a set of files with the .cs extension) into an assembly. An assembly is the unit of packaging and deployment in .NET. An assembly can be either an application or a library. A normal console or Windows application has an entry point, whereas a
5 library does not. e 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). e 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”.) To invoke the compiler, you can either use an integrated development 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 download the .NET 6 SDK, and then create a new project, as follows: dotnet new console -o MyFirstProgram cd MyFirstProgram is 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). e output will be written to a subdirectory under bin\debug, which will include MyFirstProgram.dll (the output assembly) as well as MyFirstProgram.exe (which runs the compiled program directly). 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., myVariable), and all other identifiers should be in Pascal case (e.g., MyMethod). Keywords are names that mean something special to the compiler. ere 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:
6 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 e @ 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. e contextual keywords are as follows: add alias and ascending async await by descending dynamic equals from get global group init into join let managed nameof nint not notnull nuint on or orderby partial remove 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. e 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:
7 Console.WriteLine (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10); An operator transforms and combines expressions. Most operators in C# are denoted with a symbol, such as the multiplication 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. e 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 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”). 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. e 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. e int type is a predefined 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;
8 Another predefined C# type is string. e 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: 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 e predefined bool type has exactly two possible values: true and false. e 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"); e 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 { ratio = unitRatio; } public int Convert (int unit) // Method { return unit * ratio;
9 } } Members of a type A type contains data members and function members. e data member of UnitConverter is the field called ratio. e 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. e 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. Similarly, our custom UnitConverter type acts as a blueprint for unit conversions. It holds data—the ratio—and provides function members to use that data. Constructors and instantiation Data is created by instantiating a type. You can instantiate predefined types simply by using a literal such as 12 or "Hello world". e new operator creates instances of a custom type. We started our program by creating two instances of the UnitConverter type. Immediately aer 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: public UnitConverter (int unitRatio) // Constructor { ratio = unitRatio; } Instance versus static members e data members and function members that operate on the instance of the type are called instance members. Uni t Co nverter’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.WriteLine() 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
10 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 } } Attempting to evaluate p1.Population or Panda.Name will generate a compile-time error. The public keyword e 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 members 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”. Defining a Main method All of our examples so far have used top-level statements, a feature 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); } }
11 In the absence of top-level statements, C# looks for a static method called Main, which becomes the entry point. e Main method can be defined inside any class (and only one Main method can exist). Should your Main method need to access private members of a particular class, defining a Main method inside that class can be simpler than using top-level statements. e 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). e 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 aer the element type. We describe them in “Arrays”. (e Main method can also be declared async and return a Task or Task<int> in support of asynchronous programming—see “Asynchronous Functions”.) Top-level statements Top-level statements (from C# 9) 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: 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. is means that the methods in your top-level statements become local methods (we describe the subtleties in “Local methods”). 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 conversions 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):
12 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. 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. e fundamental difference between value types and reference types is how they are handled in memory. Value types e 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 e 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
13 Figure 2 shows that p1 and p2 have independent storage. Figure 2. Assignment copies a value type instance Reference types A reference type is more complex than a value type, having two parts: an object and the reference to that object. e content of a reference type variable or constant is a reference to an object that contains the value. Here is the Point type from our previous example rewritten as a class (see Figure 3): public class Point { public int X, Y; } Figure 3. A reference type instance in memory Assigning a reference type variable copies the reference, not the object instance. is allows multiple variables to refer to the same object—something that’s not ordinarily possible with value types. If we repeat the previous example, but with Point now a class, an operation via p1 affects p2: Point p1 = new Point(); p1.X = 7; Point p2 = p1; // Copies p1 reference 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); // 9 Figure 4 shows that p1 and p2 are two references that point to the same object.
14 Figure 4. Assignment copies a reference Null A reference can be assigned the literal null, indicating that the reference points to no object. Assuming Point is a class: Point p = null; Console.WriteLine (p == null); // True Accessing a member of a null reference generates a runtime error: Console.WriteLine (p.X); // NullReferenceException NOTE In “Nullable Reference Types”, we describe a feature of C# that reduces accidental NullReference Excep tion errors. In contrast, a value type cannot ordinarily have a null value: struct Point {...} ... Point p = null; // Compile-time error int x = null; // Compile-time error To work around this, C# has a special construct for representing value-type nulls—see “Nullable Value Types”. Predefined Type Taxonomy e predefined types in C# are: Value types
15 Numeric: — Signed integer (sbyte, short, int, long) — Unsigned integer (byte, ushort, uint, ulong) — Real number (float, double, decimal) Logical (bool) Character (char) Reference types String (string) Object (object) Predefined types in C# alias .NET types in the System namespace. ere is only a syntactic difference between these two statements: int i = 5; System.Int32 i = 5; e set of predefined value types excluding decimal are known as primitive types in the Common Language Runtime (CLR). Primitive types are so called because they are supported directly via instructions in compiled code, which usually translates to direct support on the underlying processor. Numeric Types C# has the following predefined numeric types: C# type System type Suffix Size Range Integral—signed sbyte SByte 8 bits –27 to 27–1 short Int16 16 bits –215 to 215–1 int Int32 32 bits –231 to 231–1 long Int64 L 64 bits –263 to 263–1 nint IntPtr 32/64 bits Integral—unsigned byte Byte 8 bits 0 to 28–1 ushort UInt16 16 bits 0 to 216–1 uint UInt32 U 32 bits 0 to 232–1 ulong UInt64 UL 64 bits 0 to 264–1
16 C# type System type Suffix Size Range unint UIntPtr 32/64 bits Real float Single F 32 bits ± (~10–45 to 1038) double Double D 64 bits ± (~10–324 to 10308) decimal Decimal M 128 bits ± (~10–28 to 1028) Of the integral types, int and long are first-class citizens and are favored by both C# and the runtime. e other integral types are typically used for interoperability or when space efficiency is paramount. e nint and nuint native-sized integer types (introduced in C# 9) are sized to match the address space of the process at runtime and can be useful in helping with pointer arithmetic. We describe these in detail in Chapter 4 of C# 10 in a Nutshell. Of the real number types, float and double are called floating-point types and are typically used for scientific and graphical calculations. e decimal type is typically used for financial calculations where base-10-accurate arithmetic and high precision are required. (Technically, decimal is a floating-point type, too, although it’s not generally referred to as such.) Numeric Literals Integral-type literals can use decimal, hexadecimal, or binary notation; hexadecimal is denoted with the 0x prefix (e.g., 0x7f is equivalent to 127), and binary is denoted with the 0b prefix. Real literals can use decimal or exponential notation such as 1E06. Underscores may be inserted within (or before) a numeric literal to improve readability (e.g., 1_000_000). Numeric literal type inference By default, the compiler infers a numeric literal to be either double or an integral type: If the literal contains a decimal point or the exponential symbol (E), it is a double. Otherwise, the literal’s type is the first type in this list that can fit the literal’s value: int, uint, long, and ulong. For example: Console.Write ( 1.0.GetType()); // Double (double) Console.Write ( 1E06.GetType()); // Double (double) Console.Write ( 1.GetType()); // Int32 (int) Console.Write (0xF0000000.GetType()); // UInt32 (uint) Console.Write (0x100000000.GetType()); // Int64 (long) Numeric suffixes e numeric suffixes listed in the preceding table explicitly define the type of a literal: decimal d = 3.5M; // M = decimal (case-insensitive)
17 e suffixes U and L are rarely necessary because the uint, long, and ulong types can nearly always be either inferred or implicitly converted from int: long i = 5; // Implicit conversion from int to long e D suffix is technically redundant in that all literals with a decimal point are inferred to be double (and you can always add a decimal point to a numeric literal). e F and M suffixes are the most useful and are mandatory when you’re specifying fractional float or decimal literals. Without suffixes, the following would not compile because 4.5 would be inferred to be of type double, which has no implicit conversion to float or decimal: float f = 4.5F; // Won't compile without suffix decimal d = -1.23M; // Won't compile without suffix Numeric Conversions Integral-to-integral conversions Integral conversions are implicit when the destination type can represent every possible value of the source type. Otherwise, an explicit conversion is required. For example: int x = 12345; // int is a 32-bit integral type long y = x; // Implicit conversion to 64-bit int short z = (short)x; // Explicit conversion to 16-bit int Real-to-real conversions A float can be implicitly converted to a double because a double can represent every possible float value. e reverse conversion must be explicit. Conversions between decimal and other real types must be explicit. Real-to-integral conversions Conversions from integral types to real types are implicit, whereas the reverse must be explicit. Converting from a floating-point to an integral type truncates any fractional portion; to perform rounding conversions, use the static System.Convert class. A caveat is that implicitly converting a large integral type to a floating-point type preserves magnitude but might occasionally lose precision: int i1 = 100000001; float f = i1; // Magnitude preserved, precision lost int i2 = (int)f; // 100000000 Arithmetic Operators e arithmetic operators (+, -, *, /, %) are defined for all numeric types except the 8- and 16-bit integral types. e % operator evaluates the remainder aer division.
18 Increment and Decrement Operators e increment and decrement operators (++, --, respectively) increment and decrement numeric types by 1. e operator can either precede or follow the variable, depending on whether you want the variable to be updated before or aer the expression is evaluated. For example: int x = 0; Console.WriteLine (x++); // Outputs 0; x is now 1 Console.WriteLine (++x); // Outputs 2; x is now 2 Console.WriteLine (--x); // Outputs 1; x is now 1 Specialized Integral Operations Division Division operations on integral types always eliminate the remainder (round toward zero). Dividing by a variable whose value is zero generates a runtime error (a DivideByZeroException). Dividing by the literal or constant 0 generates a compile-time error. Overflow At runtime, arithmetic operations on integral types can overflow. By default, this happens silently —no exception is thrown and the result exhibits wraparound behavior, as though the computation were done on a larger integer type and the extra significant bits discarded. For example, decrementing the minimum possible int value results in the maximum possible int value: int a = int.MinValue; a--; Console.WriteLine (a == int.MaxValue); // True The checked and unchecked operators e checked operator instructs the runtime to generate an OverflowException rather than overflowing silently when an integral-typed expression or statement exceeds the arithmetic limits of that type. e checked operator affects expressions with the ++, −−, (unary) −, +, −, *, /, and explicit conversion operators between integral types. Overflow checking incurs a small performance cost. You can use checked around either an expression or a statement block. For example: int a = 1000000, b = 1000000; int c = checked (a * b); // Checks just the expression checked // Checks all expressions { // in statement block c = a * b; ... } You can make arithmetic overflow checking the default for all expressions in a program by compiling with the /checked+ command-line switch (in Visual Studio, go to Advanced Build
19 Settings). If you then need to disable overflow checking just for specific expressions or statements, you can do so with the unchecked operator. Bitwise operators C# supports the following bitwise operators: Operator Meaning Sample expression Result ~ Complement ~0xfU 0xfffffff0U & And 0xf0 & 0x33 0x30 | Or 0xf0 | 0x33 0xf3 ^ Exclusive Or 0xff00 ^ 0x0ff0 0xf0f0 << Shi le 0x20 << 2 0x80 >> Shi right 0x20 >> 1 0x10 8- and 16-Bit Integral Types e 8- and 16-bit integral types are byte, sbyte, short, and ushort. ese types lack their own arithmetic operators, so C# implicitly converts them to larger types as required. is can cause a compilation error when trying to assign the result back to a small integral type: short x = 1, y = 1; short z = x + y; // Compile-time error In this case, x and y are implicitly converted to int so that the addition can be performed. is means that the result is also an int, which cannot be implicitly cast back to a short (because it could cause loss of data). To make this compile, you must add an explicit cast: short z = (short) (x + y); // OK Special Float and Double Values Unlike integral types, floating-point types have values that certain operations treat specially. ese special values are NaN (Not a Number), +∞, −∞, and −0. e float and double classes have constants for NaN, +∞, and −∞ (as well as other values including MaxValue, MinValue, and Epsilon). For example: Console.Write (double.NegativeInfinity); // -Infinity Dividing a nonzero number by zero results in an infinite value: Console.WriteLine ( 1.0 / 0.0); // Infinity Console.WriteLine (−1.0 / 0.0); // -Infinity Console.WriteLine ( 1.0 / −0.0); // -Infinity Console.WriteLine (−1.0 / −0.0); // Infinity
Comments 0
Loading comments...
Reply to Comment
Edit Comment