using System; class Hello { static void Main() { Console.WriteLine("hello, world"); } }The source code for a C# program is typically stored in one or more text files with a file extension of .cs, as in hello.cs. Using a command-line compiler, such a program can be compiled with a command line like
csc hello.cswhich produces an application named hello.exe. The output produced by this application when it is run is:
hello, worldClose examination of this program is illuminating:
using System; class Class1 { public int Value = 0; } class Test { static void Main() { int val1 = 0; int val2 = val1; val2 = 123; Class1 ref1 = new Class1(); Class1 ref2 = ref1; ref2.Value = 123; Console.WriteLine("Values: {0}, {1}", val1, val2); Console.WriteLine("Refs: {0}, {1}", ref1.Value, ref2.Value); } }shows this difference. The output produced is
Values: 0, 123 Refs: 123, 123The assignment to the local variable val1 does not impact the local variable val2 because both local variables are of a value type (the type int) and each local variable of a value type has its own storage. In contrast, the assignment ref2.Value = 123; affects the object that both ref1 and ref2 reference. The lines
Console.WriteLine("Values: {0}, {1}", val1, val2); Console.WriteLine("Refs: {0}, {1}", ref1.Value, ref2.Value);deserve further comment, as they demonstrate some of the string formatting behavior of Console.WriteLine, which, in fact, takes a variable number of arguments. The first argument is a string, which may contain numbered placeholders like {0} and {1}. Each placeholder refers to a trailing argument with {0} referring to the second argument, {1} referring to the third argument, and so on. Before the output is sent to the console, each placeholder is replaced with the formatted value of its corresponding argument. Developers can define new value types through enum and struct declarations, and can define new reference types via class, interface, and delegate declarations. The example
using System; public enum Color { Red, Blue, Green } public struct Point { public int x, y; } public interface IBase { void F(); } public interface IDerived: IBase { void G(); } public class A { protected virtual void H() { Console.WriteLine("A.H"); } } public class B: A, IDerived { public void F() { Console.WriteLine("B.F, implementation of IDerived.F"); } public void G() { Console.WriteLine("B.G, implementation of IDerived.G"); } override protected void H() { Console.WriteLine("B.H, override of A.H"); } } public delegate void EmptyDelegate();shows an example of each kind of type declaration. Later sections describe type declarations in detail.
int i = ...; F(i); if (i = 0) // Bug: the test should be (i == 0) G();results in a compile-time error because the expression i = 0 is of type int, and if statements require an expression of type bool. The char type is used to represent Unicode characters. A variable of type char represents a single 16-bit Unicode character. The decimal type is appropriate for calculations in which rounding errors caused by floating point representations are unacceptable. Common examples include financial calculations such as tax computations and currency conversions. The decimal type provides 28 significant digits. The table below lists the predefined types, and shows how to write literal values for each of them.
Type | Description | Example |
---|---|---|
object | The ultimate base type of all other types | object o = null; |
string | String type; a string is a sequence of Unicode characters | string s = "hello"; |
sbyte | 8-bit signed integral type | sbyte val = 12; |
short | 16-bit signed integral type | short val = 12; |
int | 32-bit signed integral type | int val = 12; |
long | 64-bit signed integral type | long val1 = 12;long val2 = 34L; |
byte | 8-bit unsigned integral type | byte val1 = 12; |
ushort | 16-bit unsigned integral type | ushort val1 = 12; |
uint | 32-bit unsigned integral type | uint val1 = 12;uint val2 = 34U; |
ulong | 64-bit unsigned integral type | ulong val1 = 12;ulong val2 = 34U;ulong val3 = 56L;ulong val4 = 78UL; |
float | Single-precision floating point type | float val = 1.23F; |
double | Double-precision floating point type | double val1 = 1.23;double val2 = 4.56D; |
bool | Boolean type; a bool value is either true or false | bool val1 = true;bool val2 = false; |
char | Character type; a char value is a Unicode character | char val = 'h'; |
decimal | Precise decimal type with 28 significant digits | decimal val = 1.23M; |
using System; class Test { static void Main() { string s = "Test"; string t = string.Copy(s); Console.WriteLine(s == t); Console.WriteLine((object)s == (object)t); } }produces the output
True Falsebecause the first comparison compares two expressions of type string, and the second comparison compares two expressions of type object.
using System; class Test { static void Main() { int intValue = 123; long longValue = intValue; Console.WriteLine("{0}, {1}", intValue, longValue); } }implicitly converts an int to a long. In contrast, explicit conversions are performed with a cast expression. The example
using System; class Test { static void Main() { long longValue = Int64.MaxValue; int intValue = (int) longValue; Console.WriteLine("(int) {0} = {1}", longValue, intValue); } }uses an explicit conversion to convert a long to an int. The output is:
(int) 9223372036854775807 = -1because an overflow occurs. Cast expressions permit the use of both implicit and explicit conversions.
using System; class Test { static void Main() { int[] arr = new int[5]; for (int i = 0; i < arr.Length; i++) arr[i] = i * i; for (int i = 0; i < arr.Length; i++) Console.WriteLine("arr[{0}] = {1}", i, arr[i]); } }creates a single-dimensional array of int values, initializes the array elements, and then prints each of them out. The output produced is:
arr[0] = 0 arr[1] = 1 arr[2] = 4 arr[3] = 9 arr[4] = 16The type int[] used in the previous example is an array type. Array types are written using a non-array-type followed by one or more rank specifiers. The example
class Test { static void Main() { int[] a1; // single-dimensional array of int int[,] a2; // 2-dimensional array of int int[,,] a3; // 3-dimensional array of int int[][] j2; // "jagged" array: array of (array of int) int[][][] j3; // array of (array of (array of int)) } }shows a variety of local variable declarations that use array types with int as the element type. Array types are reference types, and so the declaration of an array variable merely sets aside space for the reference to the array. Array instances are actually created via array initializers and array creation expressions. The example
class Test { static void Main() { int[] a1 = new int[] {1, 2, 3}; int[,] a2 = new int[,] {{1, 2, 3}, {4, 5, 6}}; int[,,] a3 = new int[10, 20, 30]; int[][] j2 = new int[3][]; j2[0] = new int[] {1, 2, 3}; j2[1] = new int[] {1, 2, 3, 4, 5, 6}; j2[2] = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9}; } }shows a variety of array creation expressions. The variables a1, a2 and a3 denote rectangular arrays, and the variable j2 denotes a jagged array. It should be no surprise that these terms are based on the shapes of the arrays. Rectangular arrays always have a rectangular shape. Given the length of each dimension of the array, its rectangular shape is clear. For example, the lengths of a3's three dimensions are 10, 20, and 30, respectively, and it is easy to see that this array contains 10*20*30 elements. In contrast, the variable j2 denotes a "jagged" array, or an "array of arrays". Specifically, j2 denotes an array of an array of int, or a single-dimensional array of type int[]. Each of these int[] variables can be initialized individually, and this allows the array to take on a jagged shape. The example gives each of the int[] arrays a different length. Specifically, the length of j2[0] is 3, the length of j2[1] is 6, and the length of j2[2] is 9. [Note: In C++, an array declared as int x[3][5][7] would be considered a three dimensional rectangular array, while in C#, the declaration int[][][] declares a jagged array type. end note] The element type and shape of an array-including whether it is jagged or rectangular, and the number of dimensions it has-are part of its type. On the other hand, the size of the array-as represented by the length of each of its dimensions-is not part of an array's type. This split is made clear in the language syntax, as the length of each dimension is specified in the array creation expression rather than in the array type. For instance the declaration
int[,,] a3 = new int[10, 20, 30];has an array type of int[,,] and an array creation expression of new int[10, 20, 30]. For local variable and field declarations, a shorthand form is permitted so that it is not necessary to re-state the array type. For instance, the example
int[] a1 = new int[] {1, 2, 3};can be shortened to
int[] a1 = {1, 2, 3};without any change in program semantics. The context in which an array initializer such as {1, 2, 3} is used determines the type of the array being initialized. The example
class Test { static void Main() { short[] a = {1, 2, 3}; int[] b = {1, 2, 3}; long[] c = {1, 2, 3}; } }shows that the same array initializer syntax can be used for several different array types. Because context is required to determine the type of an array initializer, it is not possible to use an array initializer in an expression context without explicitly stating the type of the array.
using System; class Test { static void Main() { Console.WriteLine(3.ToString()); } }calls the object-defined ToString method on an integer literal, resulting in the output "3". The example
class Test { static void Main() { int i = 123; object o = i; // boxing int j = (int) o; // unboxing } }is more interesting. An int value can be converted to object and back again to int. This example shows both boxing and unboxing. When a variable of a value type needs to be converted to a reference type, an object box is allocated to hold the value, and the value is copied into the box. Unboxing is just the opposite. When an object box is cast back to its original value type, the value is copied out of the box and into the appropriate storage location. This type system unification provides value types with the benefits of object-ness without introducing unnecessary overhead. For programs that don't need int values to act like objects, int values are simply 32-bit values. For programs that need int values to behave like objects, this capability is available on demand. This ability to treat value types as objects bridges the gap between value types and reference types that exists in most languages. For example, a Stack class can provide Push and Pop methods that take and return object values.
public class Stack { public object Pop() {...} public void Push(object o) {...} }Because C# has a unified type system, the Stack class can be used with elements of any type, including value types like int.
int a; int b = 1;but it is also possible for a local variable declaration to include multiple declarators. The declarations of a and b can be rewritten as:
int a, b = 1;A variable must be assigned before its value can be obtained. The example
class Test { static void Main() { int a; int b = 1; int c = a + b; // error, a not yet assigned ... } }results in a compile-time error because it attempts to use the variable a before it is assigned a value. The rules governing definite assignment are defined in §12.3. A field (§17.4) is a variable that is associated with a class or struct, or an instance of a class or struct. A field declared with the static modifier defines a static variable, and a field declared without this modifier defines an instance variable. A static field is associated with a type, whereas an instance variable is associated with an instance. The example
using Personnel.Data; class Employee { private static DataSet ds; public string Name; public decimal Salary; ... }shows an Employee class that has a private static variable and two public instance variables. Formal parameter declarations also define variables. There are four kinds of parameters: value parameters, reference parameters, output parameters, and parameter arrays. A value parameter is used for "in" parameter passing, in which the value of an argument is passed into a method, and modifications of the parameter do not impact the original argument. A value parameter refers to its own variable, one that is distinct from the corresponding argument. This variable is initialized by copying the value of the corresponding argument. The example
using System; class Test { static void F(int p) { Console.WriteLine("p = {0}", p); p++; } static void Main() { int a = 1; Console.WriteLine("pre: a = {0}", a); F(a); Console.WriteLine("post: a = {0}", a); } }shows a method F that has a value parameter named p. The example produces the output:
pre: a = 1 p = 1 post: a = 1even though the value parameter p is modified. A reference parameter is used for "by reference" parameter passing, in which the parameter acts as an alias for a caller-provided argument. A reference parameter does not itself define a variable, but rather refers to the variable of the corresponding argument. Modifications of a reference parameter impact the corresponding argument. A reference parameter is declared with a ref modifier. The example
using System; class Test { static void Swap(ref int a, ref int b) { int t = a; a = b; b = t; } static void Main() { int x = 1; int y = 2; Console.WriteLine("pre: x = {0}, y = {1}", x, y); Swap(ref x, ref y); Console.WriteLine("post: x = {0}, y = {1}", x, y); } }shows a Swap method that has two reference parameters. The output produced is:
pre: x = 1, y = 2 post: x = 2, y = 1The ref keyword must be used in both the declaration of the formal parameter and in uses of it. The use of ref at the call site calls special attention to the parameter, so that a developer reading the code will understand that the value of the argument could change as a result of the call. An output parameter is similar to a reference parameter, except that the initial value of the caller-provided argument is unimportant. An output parameter is declared with an out modifier. The example
using System; class Test { static void Divide(int a, int b, out int result, out int remainder) { result = a / b; remainder = a % b; } static void Main() { for (int i = 1; i < 10; i++) for (int j = 1; j < 10; j++) { int ans, r; Divide(i, j, out ans, out r); Console.WriteLine("{0} / {1} = {2}r{3}", i, j, ans, r); } } }shows a Divide method that includes two output parameters-one for the result of the division and another for the remainder. For value, reference, and output parameters, there is a one-to-one correspondence between caller-provided arguments and the parameters used to represent them. A parameter array enables a many-to-one relationship: many arguments can be represented by a single parameter array. In other words, parameter arrays enable variable length argument lists. A parameter array is declared with a params modifier. There can be only one parameter array for a given method, and it must always be the last parameter specified. The type of a parameter array is always a single dimensional array type. A caller can either pass a single argument of this array type, or any number of arguments of the element type of this array type. For instance, the example
using System; class Test { static void F(params int[] args) { Console.WriteLine("# of arguments: {0}", args.Length); for (int i = 0; i < args.Length; i++) Console.WriteLine("\targs[{0}] = {1}", i, args[i]); } static void Main() { F(); F(1); F(1, 2); F(1, 2, 3); F(new int[] {1, 2, 3, 4}); } }shows a method F that takes a variable number of int arguments, and several invocations of this method. The output is:
# of arguments: 0 # of arguments: 1 args[0] = 1 # of arguments: 2 args[0] = 1 args[1] = 2 # of arguments: 3 args[0] = 1 args[1] = 2 args[2] = 3 # of arguments: 4 args[0] = 1 args[1] = 2 args[2] = 3 args[3] = 4Most of the examples presented in this introduction use the WriteLine method of the Console class. The argument substitution behavior of this method, as exhibited in the example
int a = 1, b = 2; Console.WriteLine("a = {0}, b = {1}", a, b);is accomplished using a parameter array. The WriteLine method provides several overloaded methods for the common cases in which a small number of arguments are passed, and one method that uses a parameter array.
namespace System { public class Console { public static void WriteLine(string s) {...} public static void WriteLine(string s, object a) {...} public static void WriteLine(string s, object a, object b) {...} ... public static void WriteLine(string s, params object[] args) {...} } }
using System; public class Stack { private Node first = null; public bool Empty { get { return (first == null); } } public object Pop() { if (first == null) throw new Exception("Can't Pop from an empty Stack."); else { object temp = first.Value; first = first.Next; return temp; } } public void Push(object o) { first = new Node(o, first); } class Node { public Node Next; public object Value; public Node(object value): this(value, null) {} public Node(object value, Node next) { Next = next; Value = value; } } }shows a Stack class implemented as a linked list of Node instances. Node instances are created in the Push method and are garbage collected when no longer needed. A Node instance becomes eligible for garbage collection when it is no longer possible for any code to access it. For instance, when an item is removed from the Stack, the associated Node instance becomes eligible for garbage collection. The example
class Test { static void Main() { Stack s = new Stack(); for (int i = 0; i < 10; i++) s.Push(i); s = null; } }shows code that uses the Stack class. A Stack is created and initialized with 10 elements, and then assigned the value null. Once the variable s is assigned null, the Stack and the associated 10 Node instances become eligible for garbage collection. The garbage collector is permitted to clean up immediately, but is not required to do so. The garbage collector underlying C# may work by moving objects around in memory, but this motion is invisible to most C# developers. For developers who are generally content with automatic memory management but sometimes need fine-grained control or that extra bit of performance, C# provides the ability to write "unsafe" code. Such code can deal directly with pointer types and object addresses, however, C# requires the programmer to fix objects to temporarily prevent the garbage collector from moving them. This "unsafe" code feature is in fact a "safe" feature from the perspective of both developers and users. Unsafe code must be clearly marked in the code with the modifier unsafe, so developers can't possibly use unsafe language features accidentally, and the compiler and the execution engine work together to ensure that unsafe code cannot masquerade as safe code. These restrictions limit the use of unsafe code to situations in which the code is trusted. The example
using System; class Test { static void WriteLocations(byte[] arr) { unsafe { fixed (byte* pArray = arr) { byte* pElem = pArray; for (int i = 0; i < arr.Length; i++) { byte value = *pElem; Console.WriteLine("arr[{0}] at 0x{1:X} is {2}", i, (uint)pElem, value); pElem++; } } } } static void Main() { byte[] arr = new byte[] {1, 2, 3, 4, 5}; WriteLocations(arr); } }shows an unsafe block in a method named WriteLocations that fixes an array instance and uses pointer manipulation to iterate over the elements. The index, value, and location of each array element are written to the console. One possible example of output is:
arr[0] at 0x8E0360 is 1 arr[1] at 0x8E0361 is 2 arr[2] at 0x8E0362 is 3 arr[3] at 0x8E0363 is 4 arr[4] at 0x8E0364 is 5but, of course, the exact memory locations may be different in different executions of the application.
Section | Category | Operators |
---|---|---|
14.5 | Primary | x.y f(x) [x] x++ x-- newtypeof checked unchecked |
14.6 | Unary | + - ! ~ ++x --x (T)x |
14.7 | Multiplicative | * / % |
14.7 | Additive | + - |
14.8 | Shift | << >> |
14.9 | Relational andtype-testing | < > <= >= is as |
14.9 | Equality | == != |
14.10 | Logical AND | & |
14.10 | Logical XOR | ^ |
14.10 | Logical OR | | |
14.11 | Conditional AND | && |
14.11 | Conditional OR | || |
14.12 | Conditional | ?: |
14.13 | Assignment | = *= /= %= +-= -= <<= >>= &= ^= |= |
Statement | Example |
---|---|
Statement lists and block statements | static void Main() { F(); G(); { H(); I(); } } |
Labeled statements and goto statements | static void Main(string[] args) { if (args.Length == 0) goto done; Console.WriteLine(args.Length); done: Console.WriteLine("Done"); } |
Local constant declarations | static void Main() { const float pi = 3.14f; const int r = 123; Console.WriteLine(pi * r * r); } |
Local variable declarations | static void Main() { int a; int b = 2, c = 3; a = 1; Console.WriteLine(a + b + c); } |
Expression statements | static int F(int a, int b) { return a + b; } static void Main() { F(1, 2); // Expression statement } |
if statements | static void Main(string[] args) { if (args.Length == 0) Console.WriteLine("No args"); else Console.WriteLine("Args"); } |
switch statements | static void Main(string[] args) { switch (args.Length) { case 0: Console.WriteLine("No args"); break; case 1: Console.WriteLine("One arg"); break; default: int n = args.Length; Console.WriteLine("{0} args", n); break; } } |
while statements | static void Main(string[] args) { int i = 0; while (i < args.Length) { Console.WriteLine(args[i]); i++; } } |
do statements | static void Main() { string s; do { s = Console.ReadLine(); } while (s != "Exit"); } |
for statements | static void Main(string[] args) { for (int i = 0; i < args.Length; i++) Console.WriteLine(args[i]); } |
foreach statements | static void Main(string[] args) { foreach (string s in args) Console.WriteLine(s); } |
break statements | static void Main(string[] args) { int i = 0; while (true) { if (i == args.Length) break; Console.WriteLine(args[i++]); } } |
continue statements | static void Main(string[] args) { int i = 0; while (true) { Console.WriteLine(args[i++]); if (i < args.Length) continue; break; } } |
return statements | static int F(int a, int b) { return a + b; } static void Main() { Console.WriteLine(F(1, 2)); return; } |
throw statements and try statements | static int F(int a, int b) { if (b == 0) throw new Exception("Divide by zero"); return a / b; } static void Main() { try { Console.WriteLine(F(5, 0)); } catch(Exception e) { Console.WriteLine("Error"); } } |
checked and unchecked statements | static void Main() { int x = Int32.MaxValue; Console.WriteLine(x + 1); // Overflow checked { Console.WriteLine(x + 1); // Exception } unchecked { Console.WriteLine(x + 1); // Overflow } } |
lock statements | static void Main() { A a = ...; lock(a) { a.P = a.P + 1; } } |
using statements | static void Main() { using (Resource r = new Resource()) { r.F(); } } |
Form | Intuitive meaning |
---|---|
public | Access not limited |
protected | Access limited to the containing class or types derived from the containing class |
internal | Access limited to this program |
protectedinternal | Access limited to this program or types derived from the containing class |
private | Access limited to the containing type |
using System; class MyClass { public MyClass() { Console.WriteLine("Instance constructor"); } public MyClass(int value) { MyField = value; Console.WriteLine("Instance constructor"); } ~MyClass() { Console.WriteLine("Destructor"); } public const int MyConst = 12; public int MyField = 34; public void MyMethod(){ Console.WriteLine("MyClass.MyMethod"); } public int MyProperty { get { return MyField; } set { MyField = value; } } public int this[int index] { get { return 0; } set { Console.WriteLine("this[{0}] = {1}", index, value); } } public event EventHandler MyEvent; public static MyClass operator+(MyClass a, MyClass b) { return new MyClass(a.MyField + b.MyField); } internal class MyNestedClass {} }shows a class that contains each kind of member. The example
class Test { static void Main() { // Instance constructor usage MyClass a = new MyClass(); MyClass b = new MyClass(123); // Constant usage Console.WriteLine("MyConst = {0}", MyClass.MyConst); // Field usage a.MyField++; Console.WriteLine("a.MyField = {0}", a.MyField); // Method usage a.MyMethod(); // Property usage a.MyProperty++; Console.WriteLine("a.MyProperty = {0}", a.MyProperty); // Indexer usage a[3] = a[1] = a[2]; Console.WriteLine("a[3] = {0}", a[3]); // Event usage a.MyEvent += new EventHandler(MyHandler); // Overloaded operator usage MyClass c = a + b; } static void MyHandler(object sender, EventArgs e) { Console.WriteLine("Test.MyHandler"); } internal class MyNestedClass {} }shows uses of these members.
class Constants { public const int A = 1; public const int B = A + 1; }shows a class named Constants that has two public constants. Even though constants are considered static members, a constant declaration neither requires nor allows the modifier static. Constants can be accessed through the class, as in
using System; class Test { static void Main() { Console.WriteLine("{0}, {1}", Constants.A, Constants.B); } }which prints out the values of Constants.A and Constants.B, respectively.
class Color { internal ushort redPart; internal ushort bluePart; internal ushort greenPart; public Color(ushort red, ushort blue, ushort green) { redPart = red; bluePart = blue; greenPart = green; } public static Color Red = new Color(0xFF, 0, 0); public static Color Blue = new Color(0, 0xFF, 0); public static Color Green = new Color(0, 0, 0xFF); public static Color White = new Color(0xFF, 0xFF, 0xFF); }shows a Color class that has internal instance fields named redPart, bluePart, and greenPart, and static fields named Red, Blue, Green, and White The use of static fields in this manner is not ideal. The fields are initialized at some point before they are used, but after this initialization there is nothing to stop a client from changing them. Such a modification could cause unpredictable errors in other programs that use Color and assume that the values do not change. Readonly fields can be used to prevent such problems. Assignments to a readonly field can only occur as part of the declaration, or in an instance constructor or static constructor in the same class. A static readonly field can be assigned in a static constructor, and a non-static readonly field can be assigned in an instance constructor. Thus, the Color class can be enhanced by adding the modifier readonly to the static fields:
class Color { internal ushort redPart; internal ushort bluePart; internal ushort greenPart; public Color(ushort red, ushort blue, ushort green) { redPart = red; bluePart = blue; greenPart = green; } public static readonly Color Red = new Color(0xFF, 0, 0); public static readonly Color Blue = new Color(0, 0xFF, 0); public static readonly Color Green = new Color(0, 0, 0xFF); public static readonly Color White = new Color(0xFF, 0xFF, 0xFF); }
using System; public class Stack { public static Stack Clone(Stack s) {...} public static Stack Flip(Stack s) {...} public object Pop() {...} public void Push(object o) {...} public override string ToString() {...} ... } class Test { static void Main() { Stack s = new Stack(); for (int i = 1; i < 10; i++) s.Push(i); Stack flipped = Stack.Flip(s); Stack cloned = Stack.Clone(s); Console.WriteLine("Original stack: " + s.ToString()); Console.WriteLine("Flipped stack: " + flipped.ToString()); Console.WriteLine("Cloned stack: " + cloned.ToString()); } }shows a Stack that has several static methods (Clone and Flip) and several instance methods (Pop, Push, and ToString). Methods can be overloaded, which means that multiple methods may have the same name so long as they have unique signatures. The signature of a method consists of the name of the method and the number, modifiers, and types of its formal parameters. The signature of a method does not include the return type. The example
using System; class Test { static void F() { Console.WriteLine("F()"); } static void F(object o) { Console.WriteLine("F(object)"); } static void F(int value) { Console.WriteLine("F(int)"); } static void F(ref int value) { Console.WriteLine("F(ref int)"); } static void F(int a, int b) { Console.WriteLine("F(int, int)"); } static void F(int[] values) { Console.WriteLine("F(int[])"); } static void Main() { F(); F(1); int i = 10; F(ref i); F((object)1); F(1, 2); F(new int[] {1, 2, 3}); } }shows a class with a number of methods called F. The output produced is
F() F(int) F(ref int) F(object) F(int, int) F(int[])
public class Button { private string caption; public string Caption { get { return caption; } set { caption = value; Repaint(); } } }Properties that can be both read and written, such as Caption, include both get and set accessors. The get accessor is called when the property's value is read; the set accessor is called when the property's value is written. In a set accessor, the new value for the property is made available via an implicit parameter named value. The declaration of properties is relatively straightforward, but the real value of properties is seen when they are used. For example, the Caption property can be read and written in the same way that fields can be read and written:
Button b = new Button(); b.Caption = "ABC"; // set; causes repaint string s = b.Caption; // get b.Caption += "DEF"; // get & set; causes repaint
public delegate void EventHandler(object sender, System.EventArgs e); public class Button { public event EventHandler Click; public void Reset() { Click = null; } }the Button class defines a Click event of type EventHandler. Inside the Button class, the Click member is exactly like a private field of type EventHandler. However, outside the Button class, the Click member can only be used on the left-hand side of the += and -= operators. The += operator adds a handler for the event, and the -= operator removes a handler for the event. The example
using System; public class Form1 { public Form1() { // Add Button1_Click as an event handler for Button1's Click event Button1.Click += new EventHandler(Button1_Click); } Button Button1 = new Button(); void Button1_Click(object sender, EventArgs e) { Console.WriteLine("Button1 was clicked!"); } public void Disconnect() { Button1.Click -= new EventHandler(Button1_Click); } }shows a Form1 class that adds Button1_Click as an event handler for Button1's Click event. In the Disconnect method, that event handler is removed. For a simple event declaration such as
public event EventHandler Click;the compiler automatically provides the implementation underlying the += and -= operators. An implementer who wants more control can get it by explicitly providing add and remove accessors. For example, the Button class could be rewritten as follows:
public class Button { private EventHandler handler; public event EventHandler Click { add { handler += value; } remove { handler -= value; } } }This change has no effect on client code, but allows the Button class more implementation flexibility. For example, the event handler for Click need not be represented by a field.
using System; public struct Digit { byte value; public Digit(byte value) { if (value < 0 || value > 9) throw new ArgumentException(); this.value = value; } public Digit(int value): this((byte) value) {} public static implicit operator byte(Digit d) { return d.value; } public static explicit operator Digit(byte b) { return new Digit(b); } public static Digit operator+(Digit a, Digit b) { return new Digit(a.value + b.value); } public static Digit operator-(Digit a, Digit b) { return new Digit(a.value - b.value); } public static bool operator==(Digit a, Digit b) { return a.value == b.value; } public static bool operator!=(Digit a, Digit b) { return a.value != b.value; } public override bool Equals(object value) { if (value == null) return false; if (GetType() == value.GetType()) return this == (Digit)value; return false; } public override int GetHashCode() { return value.GetHashCode(); } public override string ToString() { return value.ToString(); } } class Test { static void Main() { Digit a = (Digit) 5; Digit b = (Digit) 3; Digit plus = a + b; Digit minus = a - b; bool equals = (a == b); Console.WriteLine("{0} + {1} = {2}", a, b, plus); Console.WriteLine("{0} - {1} = {2}", a, b, minus); Console.WriteLine("{0} == {1} = {2}", a, b, equals); } }The Digit type defines the following operators:
using System; public class Stack { private Node GetNode(int index) { Node temp = first; while (index > 0) { temp = temp.Next; index--; } return temp; } public object this[int index] { get { if (!ValidIndex(index)) throw new Exception("Index out of range."); else return GetNode(index).Value; } set { if (!ValidIndex(index)) throw new Exception("Index out of range."); else GetNode(index).Value = value; } } ... } class Test { static void Main() { Stack s = new Stack(); s.Push(1); s.Push(2); s.Push(3); s[0] = 33; // Changes the top item from 3 to 33 s[1] = 22; // Changes the middle item from 2 to 22 s[2] = 11; // Changes the bottom item from 1 to 11 } }shows an indexer for the Stack class.
using System; class Point { public double x, y; public Point() { this.x = 0; this.y = 0; } public Point(double x, double y) { this.x = x; this.y = y; } public static double Distance(Point a, Point b) { double xdiff = a.x - b.x; double ydiff = a.y - b.y; return Math.Sqrt(xdiff * xdiff + ydiff * ydiff); } public override string ToString() { return string.Format("({0}, {1})", x, y); } } class Test { static void Main() { Point a = new Point(); Point b = new Point(3, 4); double d = Point.Distance(a, b); Console.WriteLine("Distance from {0} to {1} is {2}", a, b, d); } }shows a Point class that provides two public instance constructors, one of which takes no arguments, while the other takes two double arguments. If no instance constructor is supplied for a class, then an empty one with no parameters is automatically provided.
using System; class Point { public double x, y; public Point(double x, double y) { this.x = x; this.y = y; } ~Point() { Console.WriteLine("Destructed {0}", this); } public override string ToString() { return string.Format("({0}, {1})", x, y); } }shows a Point class with a destructor.
using Personnel.Data; class Employee { private static DataSet ds; static Employee() { ds = new DataSet(...); } public string Name; public decimal Salary; ... }shows an Employee class with a static constructor that initializes a static field.
using System; class A { public void F() { Console.WriteLine("A.F"); } }shows a class A that implicitly derives from object. The example
class B: A { public void G() { Console.WriteLine("B.G"); } } class Test { static void Main() { B b = new B(); b.F(); // Inherited from A b.G(); // Introduced in B A a = b; // Treat a B as an A a.F(); } }shows a class B that derives from A. The class B inherits A's F method, and introduces a G method of its own. Methods, properties, and indexers can be virtual, which means that their implementation can be overridden in derived classes. The example
using System; class A { public virtual void F() { Console.WriteLine("A.F"); } } class B: A { public override void F() { base.F(); Console.WriteLine("B.F"); } } class Test { static void Main() { B b = new B(); b.F(); A a = b; a.F(); } }shows a class A with a virtual method F, and a class B that overrides F. The overriding method in B contains a call, base.F(), which calls the overridden method in A. A class can indicate that it is incomplete, and is intended only as a base class for other classes, by including the modifier abstract. Such a class is called an abstract class. An abstract class can specify abstract members-members that a non-abstract derived class must implement. The example
using System; abstract class A { public abstract void F(); } class B: A { public override void F() { Console.WriteLine("B.F"); } } class Test { static void Main() { B b = new B(); b.F(); A a = b; a.F(); } }introduces an abstract method F in the abstract class A. The non-abstract class B provides an implementation for this method.
class Point { public int x, y; public Point(int x, int y) { this.x = x; this.y = y; } } class Test { static void Main() { Point[] points = new Point[100]; for (int i = 0; i < 100; i++) points[i] = new Point(i, i*i); } }If Point is instead implemented as a struct, as in
struct Point { public int x, y; public Point(int x, int y) { this.x = x; this.y = y; } }only one object is instantiated-the one for the array. The Point instances are allocated in-line within the array. This optimization can be misused. Using structs instead of classes can also make an application run slower or take up more memory, as passing a struct instance by value causes a copy of that struct to be created.
interface IExample { string this[int index] { get; set; } event EventHandler E; void F(int value); string P { get; set; } } public delegate void EventHandler(object sender, EventArgs e);shows an interface that contains an indexer, an event E, a method F, and a property P. Interfaces may employ multiple inheritance. In the example
interface IControl { void Paint(); } interface ITextBox: IControl { void SetText(string text); } interface IListBox: IControl { void SetItems(string[] items); } interface IComboBox: ITextBox, IListBox {}the interface IComboBox inherits from both ITextBox and IListBox. Classes and structs can implement multiple interfaces. In the example
interface IDataBound { void Bind(Binder b); } public class EditBox: Control, IControl, IDataBound { public void Paint() {...} public void Bind(Binder b) {...} }the class EditBox derives from the class Control and implements both IControl and IDataBound. In the previous example, the Paint method from the IControl interface and the Bind method from IDataBound interface are implemented using public members on the EditBox class. C# provides an alternative way of implementing these methods that allows the implementing class to avoid having these members be public. Interface members can be implemented using a qualified name. For example, the EditBox class could instead be implemented by providing IControl.Paint and IDataBound.Bind methods.
public class EditBox: IControl, IDataBound { void IControl.Paint() {...} void IDataBound.Bind(Binder b) {...} }Interface members implemented in this way are called explicit interface members because each member explicitly designates the interface member being implemented. Explicit interface members can only be called via the interface. For example, the EditBox's implementation of the Paint method can be called only by casting to the IControl interface.
class Test { static void Main() { EditBox editbox = new EditBox(); editbox.Paint(); // error: no such method IControl control = editbox; control.Paint(); // calls EditBox's Paint implementation } }
delegate void SimpleDelegate();declares a delegate named SimpleDelegate that takes no arguments and returns no result. The example
class Test { static void F() { System.Console.WriteLine("Test.F"); } static void Main() { SimpleDelegate d = new SimpleDelegate(F); d(); } }creates a SimpleDelegate instance and then immediately calls it. There is not much point in instantiating a delegate for a method and then immediately calling that method via the delegate, as it would be simpler to call the method directly. Delegates really show their usefulness when their anonymity is used. The example
void MultiCall(SimpleDelegate d, int count) { for (int i = 0; i < count; i++) { d(); } }shows a MultiCall method that repeatedly calls a SimpleDelegate. The MultiCall method doesn't know or care about the type of the target method for the SimpleDelegate, what accessibility that method has, or whether or not that method is static. All that matters is that the target method is compatible (§22.1) with SimpleDelegate.
enum Color { Red, Blue, Green } class Shape { public void Fill(Color color) { switch(color) { case Color.Red: ... break; case Color.Blue: ... break; case Color.Green: ... break; default: break; } } }shows a Color enum and a method that uses this enum. The signature of the Fill method makes it clear that the shape can be filled with one of the given colors. The use of enums is superior to the use of integer constants-as is common in languages without enums-because the use of enums makes the code more readable and self-documenting. The self-documenting nature of the code also makes it possible for the development tool to assist with code writing and other "designer" activities. For example, the use of Color rather than int for a parameter type enables smart code editors to suggest Color values.
// HelloLibrary.cs namespace CSharp.Introduction { public class HelloMessage { public string Message { get { return "hello, world"; } } } }shows the HelloMessage class in a namespace named CSharp.Introduction. The HelloMessage class provides a read-only property named Message. Namespaces can nest, and the declaration
namespace CSharp.Introduction {...}is shorthand for two levels of namespace nesting:
namespace CSharp { namespace Introduction {...} }The next step in the componentization of "hello, world" is to write a console application that uses the HelloMessage class. The fully qualified name for the class-CSharp.Introduction.HelloMessage-could be used, but this name is quite long and unwieldy. An easier way is to use a using namespace directive, which makes it possible to use all of the types in a namespace without qualification. The example
// HelloApp.cs using CSharp.Introduction; class HelloApp { static void Main() { HelloMessage m = new HelloMessage(); System.Console.WriteLine(m.Message); } }shows a using namespace directive that refers to the CSharp.Introduction namespace. The occurrences of HelloMessage are shorthand for CSharp.Introduction.HelloMessage. C# also enables the definition and use of aliases. A using alias directive defines an alias for a type. Such aliases can be useful in situation in which name collisions occur between two class libraries, or when a small number of types from a much larger namespace are being used. The example
using MessageSource = CSharp.Introduction.HelloMessage;shows a using alias directive that defines MessageSource as an alias for the HelloMessage class. The code we have written can be compiled into a class library containing the class HelloMessage and an application containing the class HelloApp. The details of this compilation step might differ based on the compiler or tool being used. A command-line compiler might enable compilation of a class library and an application that uses that library with the following command-line invocations:
csc /target:library HelloLibrary.cs csc /reference:HelloLibrary.dll HelloApp.cswhich produce a class library named HelloLibrary.dll and an application named HelloApp.exe.
// Author A namespace A { public class Base // version 1 { } } // Author B namespace B { class Derived: A.Base { public virtual void F() { System.Console.WriteLine("Derived.F"); } } }So far, so good, but now the versioning trouble begins. The author of Base produces a new version, giving it its own method F.
// Author A namespace A { public class Base // version 2 { public virtual void F() { // added in version 2 System.Console.WriteLine("Base.F"); } } }This new version of Base should be both source and binary compatible with the initial version. (If it weren't possible to simply add a method then a base class could never evolve.) Unfortunately, the new F in Base makes the meaning of Derived's F unclear. Did Derived mean to override Base's F? This seems unlikely, since when Derived was compiled, Base did not even have an F! Further, if Derived's F does override Base's F, then it must adhere to the contract specified by Base-a contract that was unspecified when Derived was written. In some cases, this is impossible. For example, Base's F might require that overrides of it always call the base. Derived's F could not possibly adhere to such a contract. C# addresses this versioning problem by requiring developers to state their intent clearly. In the original code example, the code was clear, since Base did not even have an F. Clearly, Derived's F is intended as a new method rather than an override of a base method, since no base method named F exists. If Base adds an F and ships a new version, then the intent of a binary version of Derived is still clear-Derived's F is semantically unrelated, and should not be treated as an override. However, when Derived is recompiled, the meaning is unclear-the author of Derived may intend its F to override Base's F, or to hide it. Since the intent is unclear, the compiler produces a warning, and by default makes Derived's F hide Base's F. This course of action duplicates the semantics for the case in which Derived is not recompiled. The warning that is generated alerts Derived's author to the presence of the F method in Base. If Derived's F is semantically unrelated to Base's F, then Derived's author can express this intent-and, in effect, turn off the warning-by using the new keyword in the declaration of F.
// Author A namespace A { public class Base // version 2 { public virtual void F() { // added in version 2 System.Console.WriteLine("Base.F"); } } } // Author B namespace B { class Derived: A.Base // version 2a: new { new public virtual void F() { System.Console.WriteLine("Derived.F"); } } }On the other hand, Derived's author might investigate further, and decide that Derived's F should override Base's F. This intent can be specified by using the override keyword, as shown below.
// Author A namespace A { public class Base // version 2 { public virtual void F() { // added in version 2 System.Console.WriteLine("Base.F"); } } } // Author B namespace B { class Derived: A.Base // version 2b: override { public override void F() { base.F(); System.Console.WriteLine("Derived.F"); } } }The author of Derived has one other option, and that is to change the name of F, thus completely avoiding the name collision. Although this change would break source and binary compatibility for Derived, the importance of this compatibility varies depending on the scenario. If Derived is not exposed to other programs, then changing the name of F is likely a good idea, as it would improve the readability of the program-there would no longer be any confusion about the meaning of F.
using System; [AttributeUsage(AttributeTargets.All)] public class HelpAttribute: Attribute { public HelpAttribute(string url) { this.url = url; } public string Topic = null; private string url; public string Url { get { return url; } } }defines an attribute class named HelpAttribute, or Help for short, that has one positional parameter (string url) and one named parameter (string Topic). Positional parameters are defined by the formal parameters for public instance constructors of the attribute class, and named parameters are defined by public non-static read-write fields and properties of the attribute class. The example
[Help("http://www.mycompany.com/.../Class1.htm")] public class Class1 { [Help("http://www.mycompany.com/.../Class1.htm", Topic = "F")] public void F() {} }shows several uses of the attribute Help. Attribute information for a given program element can be retrieved at run-time by using reflection support. The example
using System; class Test { static void Main() { Type type = typeof(Class1); object[] arr = type.GetCustomAttributes(typeof(HelpAttribute), true); if (arr.Length == 0) Console.WriteLine("Class1 has no Help attribute."); else { HelpAttribute ha = (HelpAttribute) arr[0]; Console.WriteLine("Url = {0}, Topic = {1}", ha.Url, ha.Topic); } } }checks to see if Class1 has a Help attribute, and writes out the associated Topic and Url values if the attribute is present. End of informative text.
/* Hello, world program This program writes "hello, world" to the console */ class Hello { static void Main() { System.Console.WriteLine("hello, world"); } }includes a delimited comment. end example] Paragraph 31 A single-line comment begins with the characters // and extends to the end of the line. [Example: The example
// Hello, world program // This program writes "hello, world" to the console // class Hello // any name will do for this class { static void Main() { // this method must be named "Main" System.Console.WriteLine("hello, world"); } }shows several single-line comments. end example] comment :: single-line-comment delimited-comment single-line-comment :: // input-charactersopt input-characters :: input-character input-characters input-character input-character :: Any Unicode character except a new-line-character new-line-character :: Carriage return character (U+000D) Line feed character (U+000A) Line separator character (U+2028) Paragraph separator character (U+2029) delimited-comment :: /* delimited-comment-charactersopt */ delimited-comment-characters :: delimited-comment-character delimited-comment-characters delimited-comment-character delimited-comment-character :: not-asterisk * not-slash not-asterisk :: Any Unicode character except * not-slash :: Any Unicode character except / Paragraph 41 Comments do not nest. 2 The character sequences /* and */ have no special meaning within a single-line comment, and the character sequences // and /* have no special meaning within a delimited comment. Paragraph 51 Comments are not processed within character and string literals.
class Class1 { static void Test(bool \u0066) { char c = '\u0066'; if (\u0066) System.Console.WriteLine(c.ToString()); } }shows several uses of \u0066, which is the escape sequence for the letter "f". The program is equivalent to
class Class1 { static void Test(bool f) { char c = 'f'; if (f) System.Console.WriteLine(c.ToString()); } }end example]
class @class { public static void @static(bool @bool) { if (@bool) System.Console.WriteLine("true"); else System.Console.WriteLine("false"); } } class Class1 { static void M() { cl\u0061ss.st\u0061tic(true); } }defines a class named "class" with a static method named "static" that takes a parameter named "bool". Note that since Unicode escapes are not permitted in keywords, the token "cl\u0061ss" is an identifier, and is the same identifier as "@class". end example] Paragraph 41 Two identifiers are considered the same if they are identical after the following transformations are applied, in order:
EscapeSequence | Charactername | Unicodeencoding |
\' | Single quote | 0x0027 |
\" | Double quote | 0x0022 |
\\ | Backslash | 0x005C |
\0 | Null | 0x0000 |
\a | Alert | 0x0007 |
\b | Backspace | 0x0008 |
\f | Form feed | 0x000C |
\n | New line | 0x000A |
\r | Carriage return | 0x000D |
\t | Horiizontal tab | 0x0009 |
\v | Vertical quote | 0x000B |
string a = "Happy birthday, Joel"; // Happy birthday, Joel string b = @"Happy birthday, Joel"; // Happy birthday, Joel string c = "hello \t world"; // hello world string d = @"hello \t world"; // hello \t world string e = "Joe said \"Hello\" to me"; // Joe said "Hello" to me string f = @"Joe said ""Hello"" to me"; // Joe said "Hello" to me string g = "\\\\server\\share\\file.txt"; // \\server\share\file.txt string h = @"\\server\share\file.txt"; // \\server\share\file.txt string i = "one\r\ntwo\r\nthree"; string j = @"one two three";shows a variety of string literals. The last string literal, j, is a verbatim string literal that spans multiple lines. The characters between the quotation marks, including white space such as new line characters, are preserved verbatim. end example] [Note: Since a hexadecimal escape sequence can have a variable number of hex digits, the string literal "\x123" contains a single character with hex value 123. To create a string containing the character with hex value 12 followed by the character 3, one could write "\x00123" or "\x12" + "3" instead. end note] Paragraph 21 The type of a string-literal is string. Paragraph 31 Each string literal does not necessarily result in a new string instance. 2 When two or more string literals that are equivalent according to the string equality operator (§14.9.7), appear in the same assembly, these string literals refer to the same string instance. [Example: For instance, the output produced by
class Test { static void Main() { object a = "hello"; object b = "hello"; System.Console.WriteLine(a == b); } }is True because the two literals refer to the same string instance. end example]
#define A #undef B class C { #if A void F() {} #else void G() {} #endif #if B void H() {} #else void I() {} #endif }results in the exact same sequence of tokens as the program
class C { void F() {} void I() {} }Thus, whereas lexically, the two programs are quite different, syntactically, they are identical. end example]
#define Enterprise #if Professional || Enterprise #define Advanced #endif namespace Megacorp.Data { #if Advanced class PivotTable {...} #endif }is valid because the #define directives precede the first token (the namespace keyword) in the source file. end example] [Example: The following example results in a compile-time error because a #define follows real code:
#define A namespace N { #define B #if B class Class1 {} #endif }end example] Paragraph 41 A #define may define a conditional compilation symbol that is already defined, without there being any intervening #undef for that symbol. [Example: The example below defines a conditional compilation symbol A and then defines it again.
#define A #define AFor compilers that allow conditional compilation symbols to be defined as compilation options, an alternative way for such redefinition to occur is to define the symbol as a compiler option as well as in the source. end example] Paragraph 51 A #undef may "undefine" a conditional compilation symbol that is not defined. [Example: The example below defines a conditional compilation symbol A and then undefines it twice; although the second #undef has no effect, it is still valid.
#define A #undef A #undef Aend example]
#define Debug // Debugging on #undef Trace // Tracing off class PurchaseTransaction { void Commit() { #if Debug CheckConsistency(); #if Trace WriteToLog(this.ToString()); #endif #endif CommitHelper(); } }Except for pre-processing directives, skipped source code is not subject to lexical analysis. For example, the following is valid despite the unterminated comment in the #else section:
#define Debug // Debugging on class PurchaseTransaction { void Commit() { #if Debug CheckConsistency(); #else /* Do something else #endif } }Note, however, that pre-processing directives are required to be lexically correct even in skipped sections of source code. Pre-processing directives are not processed when they appear inside multi-line input elements. For example, the program:
class Hello { static void Main() { System.Console.WriteLine(@"hello, #if Debug world #else Nebraska #endif "); } }results in the output:
hello, #if Debug world #else Nebraska #endifIn peculiar cases, the set of pre-processing directives that is processed might depend on the evaluation of the pp-expression. The example:
#if X /* #else /* */ class Q { } #endifalways produces the same token stream (class Q { }), regardless of whether or not X is defined. If X is defined, the only processed directives are #if and #endif, due to the multi-line comment. If X is undefined, then three directives (#if, #else, #endif) are part of the directive set. end example]
#warning Code review needed before check-in #if Debug && Retail #error A build can't be both debug and retail #endif class Test {...}always produces a warning ("Code review needed before check-in"), and produces a compile-time error if the pre-processing identifiers Debug and Retail are both defined. Note that a pp-message can contain arbitrary text; specifically, it need not contain well-formed tokens, as shown by the single quote in the word can't. end example]
#region ... #endregioncorresponds exactly to the lexical processing of a conditional compilation directive of the form:
#if true ... #endif
static void Main() {...} static void Main(string[] args) {...} static int Main() {...} static int Main(string[] args) {...}Paragraph 21 As shown, the entry point may optionally return an int value. 2 This return value is used in application termination (§10.2). Paragraph 31 The entry point may optionally have one formal parameter, and this formal parameter may have any name. 2 If such a parameter is declared, it must obey the following constraints:
namespace Megacorp.Data { class Customer { ... } } namespace Megacorp.Data { class Order { ... } }The two namespace declarations above contribute to the same declaration space, in this case declaring two classes with the fully qualified names Megacorp.Data.Customer and Megacorp.Data.Order. Because the two declarations contribute to the same declaration space, it would have caused a compile-time error if each contained a declaration of a class with the same name. end example] [Note: As specified above, the declaration space of a block includes any nested blocks. Thus, in the following example, the F and G methods result in a compile-time error because the name i is declared in the outer block and cannot be redeclared in the inner block. However, the H and I methods are valid since the two i's are declared in separate non-nested blocks.
class A { void F() { int i = 0; if (true) { int i = 1; } } void G() { if (true) { int i = 0; } int i = 1; } void H() { if (true) { int i = 0; } if (true) { int i = 1; } } void I() { for (int i = 0; i < 10; i++) H(); for (int i = 0; i < 10; i++) H(); } }end note]
public class A { public static int X; internal static int Y; private static int Z; } internal class B { public static int X; internal static int Y; private static int Z; public class C { public static int X; internal static int Y; private static int Z; } private class D { public static int X; internal static int Y; private static int Z; } }the classes and members have the following accessibility domains:
class A { int x; static void F(B b) { b.x = 1; // Ok } } class B: A { static void F(B b) { b.x = 1; // Error, x not accessible } }the B class inherits the private member x from the A class. Because the member is private, it is only accessible within the class-body of A. Thus, the access to b.x succeeds in the A.F method, but fails in the B.F method. end example]
public class A { protected int x; static void F(A a, B b) { a.x = 1; // Ok b.x = 1; // Ok } } public class B: A { static void F(A a, B b) { a.x = 1; // Error, must access through instance of B b.x = 1; // Ok } }within A, it is possible to access x through instances of both A and B, since in either case the access takes place through an instance of A or a class derived from A. However, within B, it is not possible to access x through an instance of A, since A does not derive from B. end example]
class A {...} public class B: A {...}the B class results in a compile-time error because A is not at least as accessible as B. end example] [Example: Likewise, in the example
class A {...} public class B { A F() {...} internal A G() {...} public A H() {...} }the H method in B results in a compile-time error because the return type A is not at least as accessible as the method. end example]
interface ITest { void F(); // F() void F(int x); // F(int) void F(ref int x); // F(ref int) void F(out int x); // F(out int) void F(int x, int y); // F(int, int) int F(string s); // F(string) int F(int x); // F(int) error void F(string[] a); // F(string[]) void F(params string[] a); // F(string[]) error }Note that any ref and out parameter modifiers (§17.5.1) are part of a signature. Thus, F(int), F(ref int), and F(out int) are all unique signatures. Also, note that the return type and the params modifier are not part of a signature, so it is not possible to overload solely based on return type or on the inclusion or exclusion of the params modifier. As such, the declarations of the methods F(int) and F(params string[]) identified above, result in a compile-time error. end example]
class A { void F() { i = 1; } int i = 0; }Here, it is valid for F to refer to i before it is declared. end example] Paragraph 31 Within the scope of a local variable, it is a compile-time error to refer to the local variable in a textual position that precedes the local-variable-declarator of the local variable. [Example: For example
class A { int i = 0; void F() { i = 1; // Error, use precedes declaration int i; i = 2; } void G() { int j = (j = 1); // Valid } void H() { int a = 1, b = ++a; // Valid } }In the F method above, the first assignment to i specifically does not refer to the field declared in the outer scope. Rather, it refers to the local variable and it results in a compile-time error because it textually precedes the declaration of the variable. In the G method, the use of j in the initializer for the declaration of j is valid because the use does not precede the local-variable-declarator. In the H method, a subsequent local-variable-declarator correctly refers to a local variable declared in an earlier local-variable-declarator within the same local-variable-declaration. end example] [Note: The scoping rules for local variables are designed to guarantee that the meaning of a name used in an expression context is always the same within a block. If the scope of a local variable were to extend only from its declaration to the end of the block, then in the example above, the first assignment would assign to the instance variable and the second assignment would assign to the local variable. In certain situations but not in the exampe above, this could lead to a compile-time error if the statements of the block were later to be rearranged.) The meaning of a name within a block may differ based on the context in which the name is used. In the example
using System; class A {} class Test { static void Main() { string A = "hello, world"; string s = A; // expression context Type t = typeof(A); // type context Console.WriteLine(s); // writes "hello, world" Console.WriteLine(t.ToString()); // writes "Type: A" } }the name A is used in an expression context to refer to the local variable A and in a type context to refer to the class A. end note]
class A { int i = 0; void F() { int i = 1; } void G() { i = 1; } }within the F method, the instance variable i is hidden by the local variable i, but within the G method, i still refers to the instance variable. end example] Paragraph 21 When a name in an inner scope hides a name in an outer scope, it hides all overloaded occurrences of that name. [Example: In the example
class Outer { static void F(int i) {} static void F(string s) {} class Inner { void G() { F(1); // Invokes Outer.Inner.F F("Hello"); // Error } static void F(long l) {} } }the call F(1) invokes the F declared in Inner because all outer occurrences of F are hidden by the inner declaration. For the same reason, the call F("Hello") results in a compile-time error. end example]
class Base { public void F() {} } class Derived: Base { public void F() {} // Warning, hiding an inherited name }the declaration of F in Derived causes a warning to be reported. Hiding an inherited name is specifically not an error, since that would preclude separate evolution of base classes. For example, the above situation might have come about because a later version of Base introduced an F method that wasn't present in an earlier version of the class. Had the above situation been an error, then any change made to a base class in a separately versioned class library could potentially cause derived classes to become invalid. end example] Paragraph 41 The warning caused by hiding an inherited name can be eliminated through use of the new modifier: [Example:
class Base { public void F() {} } class Derived: Base { new public void F() {} }The new modifier indicates that the F in Derived is "new", and that it is indeed intended to hide the inherited member. end example] 2 A declaration of a new member hides an inherited member only within the scope of the new member. [Example:
class Base { public static void F() {} } class Derived: Base { new private static void F() {} // Hides Base.F in Derived only } class MoreDerived: Derived { static void G() { F(); } // Invokes Base.F }In the example above, the declaration of F in Derived hides the F that was inherited from Base, but since the new F in Derived has private access, its scope does not extend to MoreDerived. Thus, the call F() in MoreDerived.G is valid and will invoke Base.F. end example]
class A {} // A namespace X // X { class B // X.B { class C {} // X.B.C } namespace Y // X.Y { class D {} // X.Y.D } } namespace X.Y // X.Y { class E {} // X.Y.E }end example]
using System; class A { ~A() { Console.WriteLine("Destruct instance of A"); } } class B { object Ref; public B(object o) { Ref = o; } ~B() { Console.WriteLine("Destruct instance of B"); } } class Test { static void Main() { B b = new B(new A()); b = null; GC.Collect(); GC.WaitForPendingFinalizers(); } }creates an instance of class A and an instance of class B. These objects become eligible for garbage collection when the variable b is assigned the value null, since after this time it is impossible for any user-written code to access them. The output could be either
Destruct instance of A Destruct instance of Bor
Destruct instance of B Destruct instance of Abecause the language imposes no constraints on the order in which objects are garbage collected. In subtle cases, the distinction between "eligible for destruction" and "eligible for collection" can be important. For example,
using System; class A { ~A() { Console.WriteLine("Destruct instance of A"); } public void F() { Console.WriteLine("A.F"); Test.RefA = this; } } class B { public A Ref; ~B() { Console.WriteLine("Destruct instance of B"); Ref.F(); } } class Test { public static A RefA; public static B RefB; static void Main() { RefB = new B(); RefA = new A(); RefB.Ref = RefA; RefB = null; RefA = null; // A and B now eligible for destruction GC.Collect(); GC.WaitForPendingFinalizers(); // B now eligible for collection, but A is not if (RefA != null) Console.WriteLine("RefA is not null"); } }In the above program, if the garbage collector chooses to run the destructor of A before the destructor of B, then the output of this program might be:
Destruct instance of A Destruct instance of B A.F RefA is not nullNote that although the instance of A was not in use and A's destructor was run, it is still possible for methods of A (in this case, F) to be called from another destructor. Also, note that running of a destructor may cause an object to become usable from the mainline program again. In this case, the running of B's destructor caused an instance of A that was previously not in use to become accessible from the live reference RefA. After the call to WaitForPendingFinalizers, the instance of B is eligible for collection, but the instance of A is not, because of the reference RefA. To avoid confusion and unexpected behavior, it is generally a good idea for destructors to only perform cleanup on data stored in their object's own fields, and not to perform any actions on referenced objects or static fields. end example]
class A { void F() { int i = 0; int j = new int(); } }Paragraph 41 Because every value type implicitly has a public parameterless instance constructor, it is not possible for a struct type to contain an explicit declaration of a parameterless constructor. 2 A struct type is however permitted to declare parameterized instance constructors (§18.3.8).
Reserved word | Aliased type |
---|---|
sbyte | System.SByte |
byte | System.Byte |
short | System.Int16 |
ushort | System.UInt16 |
int | System.Int32 |
uint | System.UInt32 |
long | System.Int64 |
ulong | System.UInt64 |
char | System.Char |
float | System.Single |
double | System.Double |
bool | System.Boolean |
decimal | System.Decimal |
int i = int.MaxValue; // System.Int32.MaxValue constant string s = i.ToString(); // System.Int32.ToString() instance method string t = 123.ToString(); // System.Int32.ToString() instance methodend example] 2 The simple types differ from other struct types in that they permit certain additional operations:
sealed class T_Box { T value; public T_Box(T t) { value = t; } }Boxing of a value v of type T now consists of executing the expression new T_Box(v), and returning the resulting instance as a value of type object. Thus, the statements
int i = 123; object box = i;conceptually correspond to
int i = 123; object box = new int_Box(i);end example] Paragraph 31 Boxing classes like T_Box and int_Box above don't actually exist and the dynamic type of a boxed value isn't actually a class type. 2 Instead, a boxed value of type T has the dynamic type T, and a dynamic type check using the is operator can simply reference type T. [Example: For example,
int i = 123; object box = i; if (box is int) { Console.Write("Box contains an int"); }will output the string "Box contains an int" on the console. end example] Paragraph 41 A boxing conversion implies making a copy of the value being boxed. 2 This is different from a conversion of a reference-type to type object, in which the value continues to reference the same instance and simply is regarded as the less derived type object. [Example: For example, given the declaration
struct Point { public int x, y; public Point(int x, int y) { this.x = x; this.y = y; } }the following statements
Point p = new Point(10, 10); object box = p; p.x = 20; Console.Write(((Point)box).x);will output the value 10 on the console because the implicit boxing operation that occurs in the assignment of p to box causes the value of p to be copied. Had Point been declared a class instead, the value 20 would be output because p and box would reference the same instance. end example]
object box = 123; int i = (int)box;conceptually correspond to
object box = new int_Box(123); int i = ((int_Box)box).value;end example] Paragraph 31 For an unboxing conversion to a given value-type to succeed at run-time, the value of the source operand must be a reference to an object that was previously created by boxing a value of that value-type. 2 If the source operand is null or a reference to an incompatible object, a System.InvalidCastException is thrown.
class A { public static int x; int y; void F(int[] v, int a, ref int b, out int c) { int i = 1; c = a + b++; } }x is a static variable, y is an instance variable, v[0] is an array element, a is a value parameter, b is a reference parameter, c is an output parameter, and i is a local variable. end example]
if (expr) then-stmt else else-stmt
while (expr) while-body
do do-body while (expr);
for (for-initializer; for-condition; for-iterator) embedded-statementis done as if the statement were written:
{ for-initializer; while (for-condition) { embedded-statement; for-iterator; } }Paragraph 21 If the for-condition is omitted from the for statement, then evaluation of definite assignment proceeds as if for-condition were replaced with true in the above expansion.
throw expr ;Paragraph 21 The definite assignment state of v at the beginning of expr is the same as the definite assignment state of v at the beginning of stmt.
return expr ;
try try-block catch(...) catch-block-1 ... catch(...) catch-block-n
try try-block finally finally-block
try try-block catch(...) catch-block-1 ... catch(...) catch-block-n finally finally-blockis done as if the statement were a try-finally statement enclosing a try-catch statement:
try { try try-block catch(...) catch-block-1 ... catch(...) catch-block-n } finally finally-block[Example: The following example demonstrates how the different blocks of a try statement (§15.10) affect definite assignment.
class A { static void F() { int i, j; try { goto LABEL: // neither i nor j definitely assigned i = 1; // i definitely assigned } catch { // neither i nor j definitely assigned i = 3; // i definitely assigned } finally { // neither i nor j definitely assigned j = 5; // j definitely assigned } // i and j definitely assigned LABEL: // j definitely assigned } }end example]
foreach (type identifier in expr) embedded-statement
using (resource-acquisition) embedded-statement
lock (expr) embedded-statement
primary-expression (arg1, arg2, ..., argn)or an object creation expression of the form:
new type (arg1, arg2, ..., argn)
class A { static void F(int x, int y) { int i; if (x >= 0 && (i = y) >= 0) { // i definitely assigned } else { // i not definitely assigned } // i not definitely assigned } }the variable i is considered definitely assigned in one of the embedded statements of an if statement but not in the other. In the if statement in method F, the variable i is definitely assigned in the first embedded statement because execution of the expression (i = y) always precedes execution of this embedded statement. In contrast, the variable i is not definitely assigned in the second embedded statement, since x >= 0 might have tested false, resulting in the variable i's being unassigned. end example]
class A { static void G(int x, int y) { int i; if (x >= 0 || (i = y) >= 0) { // i not definitely assigned } else { // i definitely assigned } // i not definitely assigned } }the variable i is considered definitely assigned in one of the embedded statements of an if statement but not in the other. In the if statement in method G, the variable i is definitely assigned in the second embedded statement because execution of the expression (i = y) always precedes execution of this embedded statement. In contrast, the variable i is not definitely assigned in the first embedded statement, since x >= 0 might have tested false, resulting in the variable i's being unassigned. end example]
int a = 123; long b = a; // implicit conversion from int to long int c = (int) b; // explicit conversion from long to intend example] 3 Some conversions are defined by the language. 4 Programs may also define their own conversions (§13.4).
Section | Category | Operators |
---|---|---|
14.5 | Primary | x.y f(x) [x] x++ x-- newtypeof checked unchecked |
14.6 | Unary | + - ! ~ ++x --x (T)x |
14.7 | Multiplicative | * / % |
14.7 | Additive | + - |
14.8 | Shift | << >> |
14.9 | Relational andtype-testing | < > <= >= is as |
14.9 | Equality | == != |
14.10 | Logical AND | & |
14.10 | Logical XOR | ^ |
14.10 | Logical OR | | |
14.11 | Conditional AND | && |
14.11 | Conditional OR | || |
14.12 | Conditional | ?: |
14.13 | Assignment | = *= /= %= +-= -= <<= >>= &= ^= |= |
+ - ! ~ ++ -- true false[Note: Although true and false are not used explicitly in expressions, they are considered operators because they are invoked in several expression contexts: boolean expressions (§14.16) and expressions involving the conditional (§14.12), and conditional logical operators (§14.11). end note] 2 The overloadable binary operators are:
+ - * / % & | ^ << >> == != > < >= <=Paragraph 31 Only the operators listed above can be overloaded. 2 In particular, it is not possible to overload member access, method invocation, or the =, &&, ||, ?:, checked, unchecked, new, typeof, as, and is operators. Paragraph 41 When a binary operator is overloaded, the corresponding assignment operator, if any, is also implicitly overloaded. 2 For example, an overload of operator * is also an overload of operator *=. 3 This is described further in §14.13. 4 Note that the assignment operator itself (=) cannot be overloaded. 5 An assignment always performs a simple bit-wise copy of a value into a variable. Paragraph 51 Cast operations, such as (T)x, are overloaded by providing user-defined conversions (§13.4). Paragraph 61 Element access, such as a[x], is not considered an overloadable operator. 2 Instead, user-defined indexing is supported through indexers (§17.8). Paragraph 71 In expressions, operators are referenced using operator notation, and in declarations, operators are referenced using functional notation. 2 The following table shows the relationship between operator and functional notations for unary and binary operators. 3 In the first entry, op denotes any overloadable unary prefix operator. 4 In the second entry, op denotes the unary postfix ++ and --operators. 5 In the third entry, op denotes any overloadable binary operator. [Note: For an example of overloading the ++ and --operators see §17.9.1. end note]
Operator notation | Functional notation |
op x | operator op(x) |
x op | operator op(x) |
x op y | operator op(x,y) |
int operator *(int x, int y); uint operator *(uint x, uint y); long operator *(long x, long y); ulong operator *(ulong x, ulong y); float operator *(float x, float y); double operator *(double x, double y); decimal operator *(decimal x, decimal y);When overload resolution rules (§14.4.2) are applied to this set of operators, the effect is to select the first of the operators for which implicit conversions exist from the operand types. [Example: For example, for the operation b * s, where b is a byte and s is a short, overload resolution selects operator *(int, int) as the best operator. Thus, the effect is that b and s are converted to int, and the type of the result is int. Likewise, for the operation i * d, where i is an int and d is a double, overload resolution selects operator *(double, double) as the best operator. end example] End of informative text.
decimal AddPercent(decimal x, double percent) { return x * (1.0 + percent / 100.0); }a compile-time error occurs because a decimal cannot be multiplied by a double. The error is resolved by explicitly converting the second operand to decimal, as follows:
decimal AddPercent(decimal x, double percent) { return x * (decimal)(1.0 + percent / 100.0); }end example] End of informative text.
Construct | Example | Description |
---|---|---|
Methodinvocation | F(x,y) | Overload resolution is applied to select the best method F in the containing class or struct. The method is invoked with the argument list (x, y). If the method is not static, the instance expression is this. |
T.F(x,y) | Overload resolution is applied to select the best method F in the class or struct T. A compile-time error occurs if the method is not static. The method is invoked with the argument list (x, y). | |
e.F(x,y) | Overload resolution is applied to select the best method F in the class, struct, or interface given by the type of e. A compile-time error occurs if the method is static. The method is invoked with the instance expression e and the argument list (x, y). | |
Propertyaccess | P | The get accessor of the property P in the containing class or struct is invoked. A compile-time error occurs if P is writeonly. If P is not static, the instance expression is this. |
P = value | The set accessor of the property P in the containing class or struct is invoked with the argument list (value). A compiletime error occurs if P is read-only. If P is not static, the instance expression is this. | |
T.P | The get accessor of the property P in the class or struct T is invoked. A compile-time error occurs if P is not static or if P is write-only. | |
T.P = value | The set accessor of the property P in the class or struct T is invoked with the argument list (value). A compile-time error occurs if P is not static or if P is read-only. | |
e.P | The get accessor of the property P in the class, struct, or interface given by the type of e is invoked with the instance expression e. A compile-time error occurs if P is static or if P is write-only. | |
e.P = value | The set accessor of the property P in the class, struct, or interface given by the type of e is invoked with the instance expression e and the argument list (value). A compile-time error occurs if P is static or if P is read-only. | |
Eventaccess | E += value | The add accessor of the event E in the containing class or struct is invoked. If E is not static, the instance expression is this. |
E -= value | The remove accessor of the event E in the containing class or struct is invoked. If E is not static, the instance expression is this. | |
T.E += value | The add accessor of the event E in the class or struct T is invoked. A compile-time error occurs if E is not static. | |
T.E -= value | The remove accessor of the event E in the class or struct T is invoked. A compile-time error occurs if E is not static. | |
e.E += value | The add accessor of the event E in the class, struct, or interface given by the type of e is invoked with the instance expression e. A compile-time error occurs if E is static. | |
e.E -= value | The remove accessor of the event E in the class, struct, or interface given by the type of e is invoked with the instance expression e. A compile-time error occurs if E is static. | |
Indexeraccess | e[x,y] | Overload resolution is applied to select the best indexer in the class, struct, or interface given by the type of e. The get accessor of the indexer is invoked with the instance expression e and the argument list (x, y). A compile-time error occurs if the indexer is write-only. |
e[x,y] = value | Overload resolution is applied to select the best indexer in the class, struct, or interface given by the type of e. The set accessor of the indexer is invoked with the instance expression e and the argument list (x, y, value). A compile-time error occurs if the indexer is read-only. | |
Operatorinvocation | -x | Overload resolution is applied to select the best unary operator in the class or struct given by the type of x. The selected operator is invoked with the argument list (x). |
x + y | Overload resolution is applied to select the best binary operator in the classes or structs given by the types of x and y. The selected operator is invoked with the argument list (x, y). | |
Instanceconstructorinvocation | new T(x,y) | Overload resolution is applied to select the best instance constructor in the class or struct T. The instance constructor is invoked with the argument list (x, y). |
class Test { static void F(int x, int y, int z) { System.Console.WriteLine("x = {0}, y = {1}, z = {2}", x, y, z); } static void Main() { int i = 0; F(i++, i++, i++); } }produces the output
x = 0, y = 1, z = 2end example] Paragraph 81 The array covariance rules (§19.5) permit a value of an array type A[] to be a reference to an instance of an array type B[], provided an implicit reference conversion exists from B to A. 2 Because of these rules, when an array element of a reference-type is passed as a reference or output parameter, a run-time check is required to ensure that the actual element type of the array is identical to that of the parameter. [Example: In the example
class Test { static void F(ref object x) {...} static void Main() { object[] a = new object[10]; object[] b = new string[10]; F(ref a[0]); // Ok F(ref b[1]); // ArrayTypeMismatchException } }the second invocation of F causes a System.ArrayTypeMismatchException to be thrown because the actual element type of b is string and not object. end example] Paragraph 91 When a function member with a parameter array is invoked in its expanded form, the invocation is processed exactly as if an array creation expression with an array initializer (§14.5.10.2) was inserted around the expanded parameters. [Example: For example, given the declaration
void F(int x, int y, params object[] args);the following invocations of the expanded form of the method
F(10, 20); F(10, 20, 30, 40); F(10, 20, 1, "hello", 3.0);correspond exactly to
F(10, 20, new object[] {}); F(10, 20, new object[] {30, 40}); F(10, 20, new object[] {1, "hello", 3.0});end example] 2 In particular, note that an empty array is created when there are zero arguments given for the parameter array.
object o = new int[3][1];which would otherwise be interpreted as
object o = (new int[3])[1];
class Test { double x; void F(bool b) { x = 1.0; if (b) { int x = 1; } } }results in a compile-time error because x refers to different entities within the outer block (the extent of which includes the nested block in the if statement). 2 In contrast, the example
class Test { double x; void F(bool b) { if (b) { x = 1.0; } else { int x = 1; } } }is permitted because the name x is never used in the outer block. Paragraph 31 Note that the rule of invariant meaning applies only to simple names. 2 It is perfectly valid for the same identifier to have one meaning as a simple name and another meaning as right operand of a member access (§14.5.4). [Example: For example:
struct Point { int x, y; public Point(int x, int y) { this.x = x; this.y = y; } }The example above illustrates a common pattern of using the names of fields as parameter names in an instance constructor. In the example, the simple names x and y refer to the parameters, but that does not prevent the member access expressions this.x and this.y from accessing the fields. end example]
struct Color { public static readonly Color White = new Color(...); public static readonly Color Black = new Color(...); public Color Complement() {...} } class A { public Color Color; // Field Color of type Color void F() { Color = Color.Black; // References Color.Black static member Color = Color.Complement(); // Invokes Complement() on Color field } static void G() { Color c = Color.White; // References Color.White static member } }Within the A class, those occurrences of the Color identifier that reference the Color type are underlined, and those that reference the Color field are not underlined. end example]
new int[,] {{0, 1}, {2, 3}, {4, 5}}exactly corresponds to
new int[3, 2] {{0, 1}, {2, 3}, {4, 5}}Paragraph 61 Array initializers are described further in §19.6. Paragraph 71 The result of evaluating an array creation expression is classified as a value, namely a reference to the newly allocated array instance. 2 The run-time processing of an array creation expression consists of the following steps:
int[][] a = new int[100][];creates a single-dimensional array with 100 elements of type int[]. The initial value of each element is null. end example] 2 It is not possible for the same array creation expression to also instantiate the sub-arrays, and the statement
int[][] a = new int[100][5]; // Errorresults in a compile-time error. 3 Instantiation of the sub-arrays must instead be performed manually, as in
int[][] a = new int[100][]; for (int i = 0; i < 100; i++) a[i] = new int[5];Paragraph 91 When an array of arrays has a "rectangular" shape, that is when the sub-arrays are all of the same length, it is more efficient to use a multi-dimensional array. 2 In the example above, instantiation of the array of arrays creates 101 objects-one outer array and 100 sub-arrays. 3 In contrast,
int[,] = new int[100, 5];creates only a single object, a two-dimensional array, and accomplishes the allocation in a single statement.
delegate double DoubleFunc(double x); class A { DoubleFunc f = new DoubleFunc(Square); static float Square(float x) { return x * x; } static double Square(double x) { return x * x; } }the A.f field is initialized with a delegate that refers to the second Square method because that method exactly matches the formal parameter list and return type of DoubleFunc. Had the second Square method not been present, a compile-time error would have occurred. end example]
using System; class Test { static void Main() { Type[] t = { typeof(int), typeof(System.Int32), typeof(string), typeof(double[]), typeof(void) }; for (int i = 0; i < t.Length; i++) { Console.WriteLine(t[i].FullName); } } }produces the following output:
System.Int32 System.Int32 System.String System.Double[] System.VoidNote that int and System.Int32 are the same type. end example]
class Test { static readonly int x = 1000000; static readonly int y = 1000000; static int F() { return checked(x * y); // Throws OverflowException } static int G() { return unchecked(x * y); // Returns -727379968 } static int H() { return x * y; // Depends on default } }no compile-time errors are reported since neither of the expressions can be evaluated at compile-time. At run-time, the F method throws a System.OverflowException, and the G method returns -727379968 (the lower 32 bits of the out-of-range result). The behavior of the H method depends on the default overflow checking context for the compilation, but it is either the same as F or the same as G. end example] [Example: In the example
class Test { const int x = 1000000; const int y = 1000000; static int F() { return checked(x * y); // Compile error, overflow } static int G() { return unchecked(x * y); // Returns -727379968 } static int H() { return x * y; // Compile error, overflow } }the overflows that occur when evaluating the constant expressions in F and H cause compile-time errors to be reported because the expressions are evaluated in a checked context. An overflow also occurs when evaluating the constant expression in G, but since the evaluation takes place in an unchecked context, the overflow is not reported. end example] Paragraph 81 The checked and unchecked operators only affect the overflow checking context for those operations that are textually contained within the "(" and ")" tokens. 2 The operators have no effect on function members that are invoked as a result of evaluating the contained expression. [Example: In the example
class Test { static int Multiply(int x, int y) { return x * y; } static int F() { return checked(Multiply(1000000, 1000000)); } }the use of checked in F does not affect the evaluation of x * y in Multiply, so x * y is evaluated in the default overflow checking context. end example] Paragraph 91 The unchecked operator is convenient when writing constants of the signed integral types in hexadecimal notation. [Example: For example:
class Test { public const int AllBits = unchecked((int)0xFFFFFFFF); public const int HighBit = unchecked((int)0x80000000); }Both of the hexadecimal constants above are of type uint. Because the constants are outside the int range, without the unchecked operator, the casts to int would produce compile-time errors. end example] [Note: The checked and unchecked operators and statements allow programmers to control certain aspects of some numeric calculations. However, the behavior of some numeric operators depends on their operands' data types. For example, multiplying two decimals always results in an exception on overflow even within an explicitly unchecked construct. Similarly, multiplying two floats never results in an exception on overflow even within an explicitly checked construct. In addition, other operators are never affected by the mode of checking, whether default or explicit. As a service to programmers, it is recommended that the compiler issue a warning when there is an arithmetic expression within an explicitly checked or unchecked context (by operator or statement) that cannot possibly be affected by the specified mode of checking. Since such a warning is not required, the compiler has flexibility in determining the circumstances that merit the issuance of such warnings. end note]
int operator +(int x); uint operator +(uint x); long operator +(long x); ulong operator +(ulong x); float operator +(float x); double operator +(double x); decimal operator +(decimal x);Paragraph 21 For each of these operators, the result is simply the value of the operand.
bool operator !(bool x);Paragraph 21 This operator computes the logical negation of the operand: If the operand is true, the result is false. 2 If the operand is false, the result is true.
int operator ~(int x); uint operator ~(uint x); long operator ~(long x); ulong operator ~(ulong x);Paragraph 21 For each of these operators, the result of the operation is the bitwise complement of x. Paragraph 31 Every enumeration type E implicitly provides the following bitwise complement operator:
E operator ~(E x);Paragraph 41 The result of evaluating ~x, where x is an expression of an enumeration type E with an underlying type U, is exactly the same as evaluating (E)(~(U)x).
int operator *(int x, int y); uint operator *(uint x, uint y); long operator *(long x, long y); ulong operator *(ulong x, ulong y);4 In a checked context, if the product is outside the range of the result type, a System.OverflowException is thrown. 5 In an unchecked context, overflows are not reported and any significant high-order bits outside the range of the result type are discarded.
float operator *(float x, float y); double operator *(double x, double y);7 The product is computed according to the rules of IEEE 754 arithmetic. 8 The following table lists the results of all possible combinations of nonzero finite values, zeros, infinities, and NaN's. 9 In the table, x and y are positive finite values. z is the result of x * y. 10 If the result is too large for the destination type, z is infinity. 11 If the result is too small for the destination type, z is zero.
* | +y | -y | +0 | -0 | +∞ | -∞ | NaN |
---|---|---|---|---|---|---|---|
+x | +z | -z | +0 | -0 | +∞ | -∞ | NaN |
-x | -z | +z | -0 | +0 | -∞ | +∞ | NaN |
+0 | +0 | -0 | +0 | -0 | NaN | NaN | NaN |
-0 | -0 | +0 | -0 | +0 | NaN | NaN | NaN |
+∞ | +∞ | -∞ | NaN | NaN | +∞ | -∞ | NaN |
-∞ | -∞ | +∞ | NaN | NaN | -∞ | +∞ | NaN |
NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
decimal operator *(decimal x, decimal y);13 If the resulting value is too large to represent in the decimal format, a System.OverflowException is thrown. 14 If the result value is too small to represent in the decimal format, the result is zero. 15 The scale of the result, before any rounding, is the sum of the scales of the two operands. 16 Decimal multiplication is equivalent to using the multiplication operator of type System.Decimal.
int operator /(int x, int y); uint operator /(uint x, uint y); long operator /(long x, long y); ulong operator /(ulong x, ulong y);4 If the value of the right operand is zero, a System.DivideByZeroException is thrown. 5 The division rounds the result towards zero, and the absolute value of the result is the largest possible integer that is less than the absolute value of the quotient of the two operands. 6 The result is zero or positive when the two operands have the same sign and zero or negative when the two operands have opposite signs. 7 If the left operand is the maximum negative int or long value and the right operand is -1, an overflow occurs. 8 In a checked context, this causes a System.OverflowException to be thrown. 9 In an unchecked context, the overflow is not reported and the result is instead the value of the left operand.
float operator /(float x, float y); double operator /(double x, double y);11 The quotient is computed according to the rules of IEEE 754 arithmetic. 12 The following table lists the results of all possible combinations of nonzero finite values, zeros, infinities, and NaN's. 13 In the table, x and y are positive finite values. z is the result of x / y. 14 If the result is too large for the destination type, z is infinity. 15 If the result is too small for the destination type, z is zero.
/ | +y | -y | +0 | -0 | +∞ | -∞ | NaN |
---|---|---|---|---|---|---|---|
+x | +z | -z | +∞ | -∞ | +0 | -0 | NaN |
-x | -z | +z | -∞ | +∞ | -0 | +0 | NaN |
+0 | +0 | -0 | NaN | NaN | +0 | -0 | NaN |
-0 | -0 | +0 | NaN | NaN | -0 | +0 | NaN |
+∞ | +∞ | -∞ | +∞ | -∞ | NaN | NaN | NaN |
-∞ | -∞ | +∞ | -∞ | +∞ | NaN | NaN | NaN |
NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
decimal operator /(decimal x, decimal y);17 If the value of the right operand is zero, a System.DivideByZeroException is thrown. 18 If the resulting value is too large to represent in the decimal format, a System.OverflowException is thrown. 19 If the result value is too small to represent in the decimal format, the result is zero. 20 The scale of the result, before any rounding, is the smallest scale that will preserve a result equal to the exact result. 21 Decimal division is equivalent to using the division operator of type System.Decimal.
int operator %(int x, int y); uint operator %(uint x, uint y); long operator %(long x, long y); ulong operator %(ulong x, ulong y);4 The result of x % y is the value produced by x -(x / y) * y. 5 If y is zero, a System.DivideByZeroException is thrown. 6 The remainder operator never causes an overflow.
float operator %(float x, float y); double operator %(double x, double y);8 The following table lists the results of all possible combinations of nonzero finite values, zeros, infinities, and NaN's. 9 In the table, x and y are positive finite values. z is the result of x % y and is computed as x -n * y, where n is the largest possible integer that is less than or equal to x / y. 10 This method of computing the remainder is analogous to that used for integer operands, but differs from the IEEE 754 definition (in which n is the integer closest to x / y).
% | +y | -y | +0 | -0 | +∞ | -∞ | NaN |
---|---|---|---|---|---|---|---|
+x | +z | +z | NaN | NaN | x | x | NaN |
-x | -z | -z | NaN | NaN | -x | -x | NaN |
+0 | +0 | +0 | NaN | NaN | +0 | +0 | NaN |
-0 | -0 | -0 | NaN | NaN | -0 | -0 | NaN |
+∞ | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
-∞ | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
decimal operator %(decimal x, decimal y);12 If the value of the right operand is zero, a System.DivideByZeroException is thrown. 13 If the resulting value is too large to represent in the decimal format, a System.OverflowException is thrown. 14 If the result value is too small to represent in the decimal format, the result is zero. 15 The scale of the result, before any rounding, is the same as the scale of y, and the sign of the result, if non-zero, is the same as that of x. 16 Decimal remainder is equivalent to using the remainder operator of type System.Decimal.
int operator +(int x, int y); uint operator +(uint x, uint y); long operator +(long x, long y); ulong operator +(ulong x, ulong y);5 In a checked context, if the sum is outside the range of the result type, a System.OverflowException is thrown. 6 In an unchecked context, overflows are not reported and any significant high-order bits outside the range of the result type are discarded.
float operator +(float x, float y); double operator +(double x, double y);8 The sum is computed according to the rules of IEEE 754 arithmetic. 9 The following table lists the results of all possible combinations of nonzero finite values, zeros, infinities, and NaN's. 10 In the table, x and y are nonzero finite values, and z is the result of x + y. 11 If x and y have the same magnitude but opposite signs, z is positive zero. 12 If x + y is too large to represent in the destination type, z is an infinity with the same sign as x + y. 13 If x + y is too small to represent in the destination type, z is a zero with the same sign as x + y.
+ | y | +0 | -0 | +∞ | -∞ | NaN |
---|---|---|---|---|---|---|
x | z | x | x | +∞ | -∞ | NaN |
+0 | y | +0 | +0 | +∞ | -∞ | NaN |
-0 | y | +0 | -0 | +∞ | -∞ | NaN |
+∞ | +∞ | +∞ | +∞ | +∞ | NaN | NaN |
-∞ | -∞ | -∞ | -∞ | NaN | -∞ | NaN |
NaN | NaN | NaN | NaN | NaN | NaN | NaN |
decimal operator +(decimal x, decimal y);15 If the resulting value is too large to represent in the decimal format, a System.OverflowException is thrown. 16 The scale of the result, before any rounding, is the larger of the scales of the two operands. 17 Decimal addition is equivalent to using the addition operator of type System.Decimal.
E operator +(E x, U y); E operator +(U x, E y);20 The operators are evaluated exactly as (E)((U)x + (U)y).
string operator +(string x, string y); string operator +(string x, object y); string operator +(object x, string y);22 The binary + operator performs string concatenation when one or both operands are of type string. 23 If an operand of string concatenation is null, an empty string is substituted. 24 Otherwise, any non-string argument is converted to its string representation by invoking the virtual ToString method inherited from type object. 25 If ToString returns null, an empty string is substituted. [Example:
using System; class Test { static void Main() { string s = null; Console.WriteLine("s = >" + s + "<"); // displays s = >< int i = 1; Console.WriteLine("i = " + i); // displays i = 1 float f = 1.2300E+15F; Console.WriteLine("f = " + f); // displays f = 1.23E+15 decimal d = 2.900m; Console.WriteLine("d = " + d); // displays d = 2.900 } }end example] 26 The result of the string concatenation operator is a string that consists of the characters of the left operand followed by the characters of the right operand. 27 The string concatenation operator never returns a null value. 28 A System.OutOfMemoryException may be thrown if there is not enough memory available to allocate the resulting string.
D operator +(D x, D y);31 The binary + operator performs delegate combination when both operands are of some delegate type D. 32 (If the operands have different delegate types, a compile-time error occurs.) 33 If the first operand is null, the result of the operation is the value of the second operand (even if that is also null). 34 Otherwise, if the second operand is null, then the result of the operation is the value of the first operand. 35 Otherwise, the result of the operation is a new delegate instance that, when invoked, invokes the first operand and then invokes the second operand. [Note: For examples of delegate combination, see §14.7.5 and §22.3. Since System.Delegate is not a delegate type, operator + is not defined for it. end note]
int operator -(int x, int y); uint operator -(uint x, uint y); long operator -(long x, long y); ulong operator -(ulong x, ulong y);4 In a checked context, if the difference is outside the range of the result type, a System.OverflowException is thrown. 5 In an unchecked context, overflows are not reported and any significant high-order bits outside the range of the result type are discarded.
float operator -(float x, float y); double operator -(double x, double y);7 The difference is computed according to the rules of IEEE 754 arithmetic. 8 The following table lists the results of all possible combinations of nonzero finite values, zeros, infinities, and NaNs. 9 In the table, x and y are nonzero finite values, and z is the result of x -y. 10 If x and y are equal, z is positive zero. 11 If x -y is too large to represent in the destination type, z is an infinity with the same sign as x -y. 12 If x -y is too small to represent in the destination type, z is a zero with the same sign as x -y.
- | y | +0 | -0 | +∞ | -∞ | NaN |
---|---|---|---|---|---|---|
x | z | x | x | -∞ | +∞ | NaN |
+0 | -y | +0 | +0 | -∞ | +∞ | NaN |
-0 | -y | -0 | +0 | -∞ | +∞ | NaN |
+∞ | +∞ | +∞ | +∞ | NaN | +∞ | NaN |
-∞ | -∞ | -∞ | -∞ | -∞ | NaN | NaN |
NaN | NaN | NaN | NaN | NaN | NaN | NaN |
decimal operator {UNICODE_150}(decimal x, decimal y);14 If the resulting value is too large to represent in the decimal format, a System.OverflowException is thrown. 15 The scale of the result, before any rounding, is the larger of the scales of the two operands. 16 Decimal subtraction is equivalent to using the subtraction operator of type System.Decimal.
U operator -(E x, E y);19 This operator is evaluated exactly as (U)((U)x -(U)y). 20 In other words, the operator computes the difference between the ordinal values of x and y, and the type of the result is the underlying type of the enumeration.
E operator -(E x, U y);21 This operator is evaluated exactly as (E)((U)x -y). 22 In other words, the operator subtracts a value from the underlying type of the enumeration, yielding a value of the enumeration.
D operator -(D x, D y);25 The binary -operator performs delegate removal when both operands are of some delegate type D. 26 (If the operands have different delegate types, a compile-time error occurs.) 27 If the first operand is null, the result of the operation is null. 28 Otherwise, if the second operand is null, then the result of the operation is the value of the first operand. 29 Otherwise, both operands represent invocation lists (§22.1) having one or more entries, and the result is a new invocation list consisting of the first operand's list with the second operand's entries removed from it, provided the second operand's list is a proper contiguous subset of the first's. 30 (For determining subset equality, corresponding entries are compared as for the delegate equality operator (§14.9.8).) 31 Otherwise, the result is the value of the left operand. 32 Neither of the operands' lists is changed in the process. 33 If the second operand's list matches multiple subsets of contiguous entries in the first operand's list, the right-most matching subset of contiguous entries is removed. 34 If removal results in an empty list, the result is null. [Example: For example:
using System; delegate void D(int x); class Test { public static void M1(int i) { /* ... */ } public static void M2(int i) { /* ... */ } } class Demo { static void Main() { D cd1 = new D(Test.M1); D cd2 = new D(Test.M2); D cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1 cd3 -= cd1; // => M1 + M2 + M2 cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1 cd3 -= cd1 + cd2; // => M2 + M1 cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1 cd3 -= cd2 + cd2; // => M1 + M1 cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1 cd3 -= cd2 + cd1; // => M1 + M2 cd3 = cd1 + cd2 + cd2 + cd1; // M1 + M2 + M2 + M1 cd3 -= cd1 + cd1; // => M1 + M2 + M2 + M1 } }end example]
int operator <<(int x, int count); uint operator <<(uint x, int count); long operator <<(long x, int count); ulong operator <<(ulong x, int count);3 The << operator shifts x left by a number of bits computed as described below. 4 The high-order bits outside the range of the result type of x are discarded, the remaining bits are shifted left, and the low-order empty bit positions are set to zero.
int operator >>(int x, int count); uint operator >>(uint x, int count); long operator >>(long x, int count); ulong operator >>(ulong x, int count);6 The >> operator shifts x right by a number of bits computed as described below. 7 When x is of type int or long, the low-order bits of x are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero if x is non-negative and set to one if x is negative. 8 When x is of type uint or ulong, the low-order bits of x are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero.
Operation | Result |
x == y | true if x is equal to y, false otherwise |
x != y | true if x is not equal to y, false otherwise |
x < y | true if x is less than y, false otherwise |
x > y | true if x is greater than y, false otherwise |
x <= y | true if x is less than or equal to y, false otherwise |
x >= y | true if x is greater than or equal to y, false otherwise |
bool operator ==(int x, int y); bool operator ==(uint x, uint y); bool operator ==(long x, long y); bool operator ==(ulong x, ulong y); bool operator !=(int x, int y); bool operator !=(uint x, uint y); bool operator !=(long x, long y); bool operator !=(ulong x, ulong y); bool operator <(int x, int y); bool operator <(uint x, uint y); bool operator <(long x, long y); bool operator <(ulong x, ulong y); bool operator >(int x, int y); bool operator >(uint x, uint y); bool operator >(long x, long y); bool operator >(ulong x, ulong y); bool operator <=(int x, int y); bool operator <=(uint x, uint y); bool operator <=(long x, long y); bool operator <=(ulong x, ulong y); bool operator >=(int x, int y); bool operator >=(uint x, uint y); bool operator >=(long x, long y); bool operator >=(ulong x, ulong y);Paragraph 21 Each of these operators compares the numeric values of the two integer operands and returns a bool value that indicates whether the particular relation is true or false.
bool operator ==(float x, float y); bool operator ==(double x, double y); bool operator !=(float x, float y); bool operator !=(double x, double y); bool operator <(float x, float y); bool operator <(double x, double y); bool operator >(float x, float y); bool operator >(double x, double y); bool operator <=(float x, float y); bool operator <=(double x, double y); bool operator >=(float x, float y); bool operator >=(double x, double y);Paragraph 21 The operators compare the operands according to the rules of the IEEE 754 standard:
{UNICODE_150}{UNICODE_8734} < {UNICODE_150}max < {UNICODE_133} < {UNICODE_150}min < {UNICODE_150}0.0 == +0.0 < +min < {UNICODE_133} < +max < +{UNICODE_8734}where min and max are the smallest and largest positive finite values that can be represented in the given floating-point format. 6 Notable effects of this ordering are:
bool operator ==(decimal x, decimal y); bool operator !=(decimal x, decimal y); bool operator <(decimal x, decimal y); bool operator >(decimal x, decimal y); bool operator <=(decimal x, decimal y); bool operator >=(decimal x, decimal y);Paragraph 21 Each of these operators compares the numeric values of the two decimal operands and returns a bool value that indicates whether the particular relation is true or false. 2 Each decimal comparison is equivalent to using the corresponding relational or equality operator of type System.Decimal.
bool operator ==(bool x, bool y); bool operator !=(bool x, bool y);Paragraph 21 The result of == is true if both x and y are true or if both x and y are false. 2 Otherwise, the result is false. Paragraph 31 The result of != is false if both x and y are true or if both x and y are false. 2 Otherwise, the result is true. 3 When the operands are of type bool, the != operator produces the same result as the ^ operator.
bool operator ==(E x, E y); bool operator !=(E x, E y); bool operator <(E x, E y); bool operator >(E x, E y); bool operator <=(E x, E y); bool operator >=(E x, E y);Paragraph 21 The result of evaluating x op y, where x and y are expressions of an enumeration type E with an underlying type U, and op is one of the comparison operators, is exactly the same as evaluating ((U)x) op ((U)y). 2 In other words, the enumeration type comparison operators simply compare the underlying integral values of the two operands.
bool operator ==(object x, object y); bool operator !=(object x, object y);Paragraph 21 The operators return the result of comparing the two references for equality or non-equality. Paragraph 31 Since the predefined reference type equality operators accept operands of type object, they apply to all types that do not declare applicable operator == and operator != members. 2 Conversely, any applicable user-defined equality operators effectively hide the predefined reference type equality operators. Paragraph 41 The predefined reference type equality operators require the operands to be reference-type values or the value null; furthermore, they require that a standard implicit conversion (§13.3.1) exists from the type of either operand to the type of the other operand. 2 Unless both of these conditions are true, a compile-time error occurs. [Note: Notable implications of these rules are:
using System; class Test { static void Main() { string s = "Test"; string t = string.Copy(s); Console.WriteLine(s == t); Console.WriteLine((object)s == t); Console.WriteLine(s == (object)t); Console.WriteLine((object)s == (object)t); } }produces the output
True False False FalseThe s and t variables refer to two distinct string instances containing the same characters. The first comparison outputs True because the predefined string equality operator (§14.9.7) is selected when both operands are of type string. The remaining comparisons all output False because the predefined reference type equality operator is selected when one or both of the operands are of type object. Note that the above technique is not meaningful for value types. The example
class Test { static void Main() { int i = 123; int j = 123; System.Console.WriteLine((object)i == (object)j); } }outputs False because the casts create references to two separate instances of boxed int values. end example]
bool operator ==(string x, string y); bool operator !=(string x, string y);Paragraph 21 Two string values are considered equal when one of the following is true: Paragraph 31 The string equality operators compare string values rather than string references. 2 When two separate string instances contain the exact same sequence of characters, the values of the strings are equal, but the references are different. [Note: As described in §14.9.6, the reference type equality operators can be used to compare string references instead of string values. end note]
bool operator ==(System.Delegate x, System.Delegate y); bool operator !=(System.Delegate x, System.Delegate y);Paragraph 21 Two delegate instances are considered equal as follows:
int operator &(int x, int y); uint operator &(uint x, uint y); long operator &(long x, long y); ulong operator &(ulong x, ulong y); int operator |(int x, int y); uint operator |(uint x, uint y); long operator |(long x, long y); ulong operator |(ulong x, ulong y); int operator ^(int x, int y); uint operator ^(uint x, uint y); long operator ^(long x, long y); ulong operator ^(ulong x, ulong y);Paragraph 21 The & operator computes the bitwise logical AND of the two operands, the | operator computes the bitwise logical OR of the two operands, and the ^ operator computes the bitwise logical exclusive OR of the two operands. 2 No overflows are possible from these operations.
E operator &(E x, E y); E operator |(E x, E y); E operator ^(E x, E y);Paragraph 21 The result of evaluating x op y, where x and y are expressions of an enumeration type E with an underlying type U, and op is one of the logical operators, is exactly the same as evaluating (E)((U)x op (U)y). 2 In other words, the enumeration type logical operators simply perform the logical operation on the underlying type of the two operands.
bool operator &(bool x, bool y); bool operator |(bool x, bool y); bool operator ^(bool x, bool y);Paragraph 21 The result of x & y is true if both x and y are true. 2 Otherwise, the result is false. Paragraph 31 The result of x | y is true if either x or y is true. 2 Otherwise, the result is false. Paragraph 41 The result of x ^ y is true if x is true and y is false, or x is false and y is true. 2 Otherwise, the result is false. 3 When the operands are of type bool, the ^ operator computes the same result as the != operator.
string[] sa = new string[10]; object[] oa = sa; oa[0] = null; // Ok oa[1] = "Hello"; // Ok oa[2] = new ArrayList(); // ArrayTypeMismatchExceptionthe last assignment causes a System.ArrayTypeMismatchException to be thrown because an instance of ArrayList cannot be stored in an element of a string[]. end note] Paragraph 51 When a property or indexer declared in a struct-type is the target of an assignment, the instance expression associated with the property or indexer access must be classified as a variable. 2 If the instance expression is classified as a value, a compile-time error occurs. [Note: Because of §14.5.4, the same rule also applies to fields. end note] [Example: Given the declarations:
struct Point { int x, y; public Point(int x, int y) { this.x = x; this.y = y; } public int X { get { return x; } set { x = value; } } public int Y { get { return y; } set { y = value; } } } struct Rectangle { Point a, b; public Rectangle(Point a, Point b) { this.a = a; this.b = b; } public Point A { get { return a; } set { a = value; } } public Point B { get { return b; } set { b = value; } } }in the example
Point p = new Point(); p.X = 100; p.Y = 100; Rectangle r = new Rectangle(); r.A = new Point(10, 10); r.B = p;the assignments to p.X, p.Y, r.A, and r.B are permitted because p and r are variables. However, in the example
Rectangle r = new Rectangle(); r.A.X = 10; r.A.Y = 10; r.B.X = 100; r.B.Y = 100;the assignments are all invalid, since r.A and r.B are not variables. end example]
byte b = 0; char ch = '\0'; int i = 0; b += 1; // Ok b += 1000; // Error, b = 1000 not permitted b += i; // Error, b = i not permitted b += (byte)i; // Ok ch += 1; // Error, ch = 1 not permitted ch += (char)1; // Okthe intuitive reason for each error is that a corresponding simple assignment would also have been an error. end example]
void F(bool b) { if (b) int i = 44; }results in a compile-time error because an if statement requires an embedded-statement rather than a statement for its if branch. If this code were permitted, then the variable i would be declared, but it could never be used. (Note, however, that by placing i's declaration in a block, the example is valid.) end example]
void F() { Console.WriteLine("reachable"); goto Label; Console.WriteLine("unreachable"); Label: Console.WriteLine("reachable"); }the second invocation of Console.WriteLine is unreachable because there is no possibility that the statement will be executed. end example] Paragraph 31 A warning is reported if the compiler determines that a statement is unreachable. 2 It is specifically not an error for a statement to be unreachable. [Note: To determine whether a particular statement or end point is reachable, the compiler performs flow analysis according to the reachability rules defined for each statement. The flow analysis takes into account the values of constant expressions (§14.15) that control the behavior of statements, but the possible values of non-constant expressions are not considered. In other words, for purposes of control flow analysis, a non-constant expression of a given type is considered to have any possible value of that type. In the example
void F() { const int i = 1; if (i == 2) Console.WriteLine("unreachable"); }the boolean expression of the if statement is a constant expression because both operands of the == operator are constants. As the constant expression is evaluated at compile-time, producing the value false, the Console.WriteLine invocation is considered unreachable. However, if i is changed to be a local variable
void F() { int i = 1; if (i == 2) Console.WriteLine("reachable"); }the Console.WriteLine invocation is considered reachable, even though, in reality, it will never be executed. end note] Paragraph 41 The block of a function member is always considered reachable. 2 By successively evaluating the reachability rules of each statement in a block, the reachability of any given statement can be determined. [Example: In the example
void F(int x) { Console.WriteLine("start"); if (x < 0) Console.WriteLine("negative"); }the reachability of the second Console.WriteLine is determined as follows:
bool ProcessMessage() {...} void ProcessMessages() { while (ProcessMessage()) ; }Also, an empty statement can be used to declare a label just before the closing "}" of a block:
void F() { ... if (done) goto exit; ... exit: ; }end example]
int F(int x) { if (x >= 0) goto x; x = -x; x: return x; }is valid and uses the name x as both a parameter and a label. end example] Paragraph 51 Execution of a labeled statement corresponds exactly to execution of the statement following the label. Paragraph 61 In addition to the reachability provided by normal flow of control, a labeled statement is reachable if the label is referenced by a reachable goto statement. 2 (Exception: If a goto statement is inside a try that includes a finally block, and the labeled statement is outside the try, and the end point of the finally block is unreachable, then the labeled statement is not reachable from that goto statement.)
void F() { int x = 1, y, z = x * 2; }corresponds exactly to
void F() { int x; x = 1; int y; int z; z = x * 2; }end example]
if (x) if (y) F(); else G();is equivalent to
if (x) { if (y) { F(); } else { G(); } }end example] Paragraph 31 An if statement is executed as follows:
switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; default: CaseOthers(); break; }is valid because no switch section has a reachable end point. Unlike C and C++, execution of a switch section is not permitted to "fall through" to the next switch section, and the example
switch (i) { case 0: CaseZero(); case 1: CaseZeroOrOne(); default: CaseAny(); }results in a compile-time error. When execution of a switch section is to be followed by execution of another switch section, an explicit goto case or goto default statement must be used:
switch (i) { case 0: CaseZero(); goto case 1; case 1: CaseZeroOrOne(); goto default; default: CaseAny(); break; }end example] Paragraph 81 Multiple labels are permitted in a switch-section. [Example: The example
switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; case 2: default: CaseTwo(); break; }is valid. The example does not violate the "no fall through" rule because the labels case 2: and default: are part of the same switch-section. end example] [Note: The "no fall through" rule prevents a common class of bugs that occur in C and C++ when break statements are accidentally omitted. In addition, because of this rule, the switch sections of a switch statement can be arbitrarily rearranged without affecting the behavior of the statement. For example, the sections of the switch statement above can be reversed without affecting the behavior of the statement:
switch (i) { default: CaseAny(); break; case 1: CaseZeroOrOne(); goto default; case 0: CaseZero(); goto case 1; }end note] [Note: The statement list of a switch section typically ends in a break, goto case, or goto default statement, but any construct that renders the end point of the statement list unreachable is permitted. For example, a while statement controlled by the boolean expression true is known to never reach its end point. Likewise, a throw or return statement always transfers control elsewhere and never reaches its end point. Thus, the following example is valid:
switch (i) { case 0: while (true) F(); case 1: throw new ArgumentException(); case 2: return; }end note] [Example: The governing type of a switch statement may be the type string. For example:
void DoCommand(string command) { switch (command.ToLower()) { case "run": DoRun(); break; case "save": DoSave(); break; case "quit": DoQuit(); break; default: InvalidCommand(command); break; } }end example] [Note: Like the string equality operators (§14.9.7), the switch statement is case sensitive and will execute a given switch section only if the switch expression string exactly matches a case label constant. end note] Paragraph 91 When the governing type of a switch statement is string, the value null is permitted as a case label constant. Paragraph 101 The statement-lists of a switch-block may contain declaration statements (§15.5). 2 The scope of a local variable or constant declared in a switch block is the switch block. Paragraph 111 Within a switch block, the meaning of a name used in an expression context must always be the same (§14.5.2.1). Paragraph 121 The statement list of a given switch section is reachable if the switch statement is reachable and at least one of the following is true:
foreach (ElementType element in collection) statementcorresponds to one of two possible expansions:
Enumerator enumerator = (collection).GetEnumerator(); try { while (enumerator.MoveNext()) { ElementType element = (ElementType)enumerator.Current; statement; } } finally { IDisposable disposable = enumerator as System.IDisposable; if (disposable != null) disposable.Dispose(); }
IEnumerator enumerator = ((System.IEnumerable)(collection)).GetEnumerator(); try { while (enumerator.MoveNext()) { ElementType element = (ElementType)enumerator.Current; statement; } } finally { IDisposable disposable = enumerator as System.IDisposable; if (disposable != null) disposable.Dispose(); }
using System; class Test { static void Main() { double[,] values = { {1.2, 2.3, 3.4, 4.5}, {5.6, 6.7, 7.8, 8.9} }; foreach (double elementValue in values) Console.Write("{0} ", elementValue); Console.WriteLine(); } }The output produced is as follows:
1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9end example]
using System; class Test { static void Main() { while (true) { try { try { Console.WriteLine("Before break"); break; } finally { Console.WriteLine("Innermost finally block"); } } finally { Console.WriteLine("Outermost finally block"); } } Console.WriteLine("After break"); } }the finally blocks associated with two try statements are executed before control is transferred to the target of the jump statement. The output produced is as follows:
Before break Innermost finally block Outermost finally block After breakend example]
using System; class Test { static void Main(string[] args) { string[,] table = { {"red", "blue", "green"}, {"Monday", "Wednesday", "Friday"} }; foreach (string str in args) { int row, colm; for (row = 0; row <= 1; ++row) for (colm = 0; colm <= 2; ++colm) if (str == table[row,colm]) goto done; Console.WriteLine("{0} not found", str); continue; done: Console.WriteLine("Found {0} at [{1}][{2}]", str, row, colm); } } }a goto statement is used to transfer control out of a nested scope. end note] 3 The target of a goto case statement is the statement list in the immediately enclosing switch statement (§15.7.2) which contains a case label with the given constant value. 4 If the goto case statement is not enclosed by a switch statement, if the constant-expression is not implicitly convertible (§13.1) to the governing type of the nearest enclosing switch statement, or if the nearest enclosing switch statement does not contain a case label with the given constant value, a compile-time error occurs. Paragraph 31 The target of a goto default statement is the statement list in the immediately enclosing switch statement (§15.7.2), which contains a default label. 2 If the goto default statement is not enclosed by a switch statement, or if the nearest enclosing switch statement does not contain a default label, a compile-time error occurs. Paragraph 41 A goto statement cannot exit a finally block (§15.10). 2 When a goto statement occurs within a finally block, the target of the goto statement must be within the same finally block, or otherwise a compile-time error occurs. Paragraph 51 A goto statement is executed as follows: