C# is a general-purpose, type-safe, object-oriented programming language, the goal of which is programmer productivity. To this end, the language balances simplicity, expressiveness, and performance. C# 8 is designed to work the Microsoft .NET Core 3 runtime, and .NET Standard 2.1 (whereas C# 7 was designed to work with the Microsoft .NET Framework 4.6/4.7/4.8 as well as .NET Core 2.x and .NET Standard 2.0).
The programs and code snippets in this book mirror those in Chapters 2 through 4 of C# 8.0 in a Nutshell 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 http://www.linqpad.net.
Here 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:
using System; // Importing namespace class Test // Class declaration { static void Main() // Method declaration { int x = 12 * 30; // Statement 1 Console.WriteLine (x); // Statement 2 } // End of method } // End of class
At the heart of this program lie two statements. Statements in C# execute sequentially and are terminated by a semicolon. The first statement computes the expression 12 * 30
and stores the result in a local variable, named x
, which is an integer type. The second statement calls the Console
class’s WriteLine
method, to print the variable x
to a text window on the screen.
A method performs an action in a series of statements, called a statement block—a pair of braces containing zero or more statements. We defined a single method named Main
.
Writing higher-level functions that call upon lower-level functions simplifies a program. We can refactor our program with a reusable method that multiplies an integer by 12, as follows:
using System; class Test { static void Main() { Console.WriteLine (FeetToInches (30)); // 360 Console.WriteLine (FeetToInches (100)); // 1200 } static int FeetToInches (int feet) { int inches = feet * 12; return inches; } }
Areturn types method can receive input data from the caller by specifying parameters, and output data back to the caller by specifying a return type. In the preceding example, we defined a method called FeetToInches
that has a parameter for inputting feet, and a return type for outputting inches, both of type int
(integer).
The literals 30
and 100
are the arguments passed to the FeetToInches
method. The Main
method in our example has empty parentheses because it has no parameters and is void
because it doesn’t return any value to its caller. C# recognizes a method called Main
as signaling the default entry point of execution. The Main
method can optionally return an integer (rather than void
) in order to return a value to the execution environment. 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) {...}
An array (such as string[]
) represents a fixed number of elements of a particular type (see “Arrays”).
Methods are one of several kinds of functions in C#. Another kind of function we used was the *
operator, which performs multiplication. There are also constructors, properties, events, indexers, and finalizers.
In our example, the two methods are grouped into a class. A class groups function members and data members to form an object-oriented building block. The Console
class groups members that handle command-line input/output functionality, such as the WriteLine
method. Our Test
class groups two methods—the Main
method and the FeetToInches
method. A class is a kind of type, which we examine later in “Type Basics”.
At the outermost level of a program, types are organized into namespaces. The using
directive makes the System
namespace available to our application to use the Console
class. We could define all of our classes within the TestPrograms
namespace, as follows:
using System; namespace TestPrograms { class Test {...} class Test2 {...} }
The .NET Core libraries are organized into nested namespaces. For example, this is the namespace that contains types for handling text:
using System.Text;
The using
directive is there for convenience; you can also refer to a type by its fully qualified name, which is the type name prefixed with its namespace, such as System.Text.StringBuilder
.
The C# compiler compiles source code, specified as a set of files with the .cs extension, into an assembly, which is the unit of packaging and deployment in .NET. An assembly can be either an application or a library, the difference being that an application has an entry point (“Main
” method), whereas a library does not. The purpose of a library is to be called upon (referenced) by an application or other libraries. The .NET Core runtime (and the .NET Framework) comprise a set of libraries.
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 Core, first download the .NET Core 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 written to a subdirectory under bindebug, which will include MyFirstProgram.dll (the output assembly) as well as MyFirstProgram.exe (which runs the compiled program directly).
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; class Test { static void Main() { int x = 12 * 30; Console.WriteLine (x); } }
Identifiers are names that programmers choose for their classes, methods, variables, and so on. These are the identifiers in our example program, in the order in which they appear:
System Test Main 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. These are the keywords in our example program:
using class static void int
Most keywords are reserved, which means that you can’t use them as identifiers. Here is the full list of C# reserved keywords:
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 ref return sbyte sealed short sizeof |
stackalloc static string struct switch this throw true try typeof uint ulong unchecked unsafe ushort using virtual void while |
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
.
Some keywords are contextual, meaning they can also be used as identifiers—without an @
symbol. The contextual keywords are as follows:
add alias ascending async await by descending |
dynamic equals from get global group into |
join let nameof on orderby partial remove |
select set value var when where yield |
With contextual keywords, ambiguity cannot arise within the context in which they are used.
Literals are primitive pieces of data lexically embedded into the program. The literals in our example program are 12
and 30
. Punctuators help demarcate the structure of the program. The punctuators in our program are {
, }
, and ;
.
The braces group multiple statements into a statement block. The semicolon terminates a (nonblock) 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 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). The parentheses, in our example, appear where we declare or call a method; empty parentheses mean that the method accepts no arguments. The equals sign performs assignment (the double equals, ==
, performs equality comparison).
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”).
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 types (also called built-in types) are types that are specially supported by the compiler. The 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;
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:
string message = "Hello world"; string upperMessage = message.ToUpper(); Console.WriteLine (upperMessage); // HELLO WORLD int x = 2015; message = message + x.ToString(); Console.WriteLine (message); // Hello world2015
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");
Just as we can build complex functions from simple functions, we 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:
using System; public class UnitConverter { int ratio; // Field public UnitConverter (int unitRatio) // Constructor { ratio = unitRatio; } public int Convert (int unit) // Method { return unit * ratio; } } class Test { static void Main() { UnitConverter feetToInches = new UnitConverter(12); UnitConverter milesToFeet = new UnitConverter(5280); Console.Write (feetToInches.Convert(30)); // 360 Console.Write (feetToInches.Convert(100)); // 1200 Console.Write (feetToInches.Convert (milesToFeet.Convert(1))); // 63360 } }
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
. 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.
Data is created by instantiating a type. We can instantiate predefined types simply by using a literal such as 12
or "Hello world"
.
The new
operator creates instances of a custom type. We started our Main
method 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:
public UnitConverter (int unitRatio) // Constructor { ratio = unitRatio; }
The data members and function members that operate on the instance of the type are called instance members. The UnitConverter
’s Convert
method and the 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, but rather on the type itself, must be marked as static
. The Test.Main
and Console.WriteLine
methods are static methods. The Console
class is actually a static class, which means all of its members are static. You never actually create instances of a Console
—one console is shared across the entire application.
Let’s contrast instance with static members. 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:
public class Panda { public string Name; // Instance field public static int Population; // Static field public Panda (string n) // Constructor { Name = n; // Assign instance field Population = Population+1; // Increment static field } }
The following code creates two instances of the Panda
, prints their names, and then prints 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
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 the Test
class could not access it. 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.
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
):
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.
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.
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; }
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
Figure 2 shows that p1
and p2
have independent storage.
A reference type is more complex than a value type, having two parts: an object and the reference to that object. The 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; }
Assigning a reference type variable copies the reference, not the object instance. This 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.
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
C# 8 introduces a new feature to reduce accidental NullReferenceException
errors. For more information, see “Nullable Reference Types (C# 8)”.
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
C# has a special construct called nullable types for representing value-type nulls. To learn more, see “Nullable (Value) Types”.
The predefined types in C# are:
Value types
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 Core types in the System
namespace. There is only a syntactic difference between these two statements:
int i = 5; System.Int32 i = 5;
The 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.
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 |
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 |
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. The other integral types are typically used for interoperability or when space efficiency is paramount.
Of the real number types, float
and double
are called floating-point types and are typically used for scientific and graphical calculations. The 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.)
Integral-typed literals can use decimal or hexadecimal notation; hexadecimal is denoted with the 0x
prefix (e.g., 0x7f
is equivalent to 127
). From C# 7, you can also use the 0b
prefix for binary literals. Real literals can use decimal or exponential notation such as 1E06
.
From C# 7, underscores may be inserted within (or before) a numeric literal to improve readability (e.g., 1_000_000
).
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)
The numeric suffixes listed in the preceding table explicitly define the type of a literal:
decimal d = 3.5M; // M = decimal (case-insensitive)
The 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
The 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). The 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
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
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
The increment and decrement operators (++
, --
, respectively) increment and decrement numeric types by 1. The operator can either precede or follow the variable, depending on whether you want the variable to be updated before or after 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
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
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. The 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 Settings). If you then need to disable overflow checking just for specific expressions or statements, you can do so with the unchecked
operator.
The 8- and 16-bit integral types are byte
, sbyte
, short
, and ushort
. These types lack their own arithmetic operators, so C# implicitly converts them to larger types as required. This 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. This 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, we must add an explicit cast:
short z = (short) (x + y); // OK
Unlike integral types, floating-point types have values that certain operations treat specially. These special values are NaN (Not a Number), +∞, −∞, and −0. The 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
Dividing zero by zero, or subtracting infinity from infinity, results in a NaN:
Console.Write ( 0.0 / 0.0); // NaN Console.Write ((1.0 / 0.0) − (1.0 / 0.0)); // NaN
When you use ==
, a NaN value is never equal to another value, even another NaN value. To test whether a value is NaN, you must use the float.IsNaN
or double.IsNaN
method:
Console.WriteLine (0.0 / 0.0 == double.NaN); // False Console.WriteLine (double.IsNaN (0.0 / 0.0)); // True
When you use object.Equals
, however, two NaN values are equal:
bool isTrue = object.Equals (0.0/0.0, double.NaN);
double
is useful for scientific computations (such as computing spatial coordinates). decimal
is useful for financial computations and values that are “man-made” rather than the result of real-world measurements. Here’s a summary of the differences:
Feature | double |
decimal |
---|---|---|
Internal representation | Base 2 | Base 10 |
Precision | 15–16 significant figures | 28–29 significant figures |
Range | ±(~10−324 to ~10308) | ±(~10−28 to ~1028) |
Special values | +0, −0, +∞, −∞, and NaN | None |
Speed | Native to processor | Non-native to processor (about 10 times slower than double ) |
float
and double
internally represent numbers in base 2. For this reason, most literals with a fractional component (which are in base 10) will not be represented precisely:
float tenth = 0.1f; // Not quite 0.1 float one = 1f; Console.WriteLine (one - tenth * 10f); // -1.490116E-08
This is why float
and double
are bad for financial calculations. In contrast, decimal
works in base 10 and so can precisely represent fractional numbers such as 0.1 (whose base-10 representation is nonrecurring).
C#’s bool
type (aliasing the System.Boolean
type) is a logical value that can be assigned the literal true
or false
.
Although a Boolean value requires only one bit of storage, the runtime will use one byte of memory because this is the minimum chunk that the runtime and processor can efficiently work with. To avoid space inefficiency in the case of arrays, .NET provides a BitArray
class in the System.Collections
namespace that is designed to use just one bit per Boolean value.
==
and !=
test for equality and inequality, respectively, of any type, and always return a bool
value. Value types typically have a very simple notion of equality:
int x = 1, y = 2, z = 1; Console.WriteLine (x == y); // False Console.WriteLine (x == z); // True
For reference types, equality, by default, is based on reference, as opposed to the actual value of the underlying object. Therefore, two instances of an object with identical data are not considered equal unless the ==
operator for that type is specially overloaded to that effect (see “The object Type” and “Operator Overloading”).
The equality and comparison operators, ==
, !=
, <
, >
, >=
, and <=
, work for all numeric types, but should be used with caution with real numbers (see “Real Number Rounding Errors” in the previous section). The comparison operators also work on enum
type members, by comparing their underlying integral values.
The &&
and ||
operators test for and and or conditions, respectively. They are frequently used in conjunction with the !
operator, which expresses not. In the following example, the UseUmbrella
method returns true
if it’s rainy or sunny (to protect us from the rain or the sun), as long as it’s not also windy (because umbrellas are useless in the wind):
static bool UseUmbrella (bool rainy, bool sunny, bool windy) { return !windy && (rainy || sunny); }
The &&
and ||
operators short-circuit evaluation when possible. In the preceding example, if it is windy, the expression (rainy || sunny)
is not even evaluated. Short-circuiting is essential in allowing expressions such as the following to run without throwing a NullReferenceException
:
if (sb != null && sb.Length > 0) ...
The &
and |
operators also test for and and or conditions:
return !windy & (rainy | sunny);
The difference is that they do not short-circuit. For this reason, they are rarely used in place of conditional operators.
The ternary conditional operator (simply called the conditional operator) has the form q ? a : b
, where if condition q
is true, a
is evaluated, else b
is evaluated. For example:
static int Max (int a, int b) { return (a > b) ? a : b; }
The conditional operator is particularly useful in LINQ queries.
C#’s char
type (aliasing the System.Char
type) represents a Unicode character and occupies two bytes (UTF-16). A char
literal is specified inside single quotes:
char c = 'A'; // Simple character
Escape sequences express characters that cannot be expressed or interpreted literally. An escape sequence is a backslash followed by a character with a special meaning. For example:
char newLine = ' '; char backSlash = '';