- •Foreword
- •Introduction
- •Scope
- •Conformance
- •Normative references
- •Definitions
- •Notational conventions
- •Acronyms and abbreviations
- •General description
- •Language overview
- •Getting started
- •Types
- •Predefined types
- •Conversions
- •Array types
- •Type system unification
- •Variables and parameters
- •Automatic memory management
- •Expressions
- •Statements
- •Classes
- •Constants
- •Fields
- •Methods
- •Properties
- •Events
- •Operators
- •Indexers
- •Instance constructors
- •Destructors
- •Static constructors
- •Inheritance
- •Static classes
- •Partial type declarations
- •Structs
- •Interfaces
- •Delegates
- •Enums
- •Namespaces and assemblies
- •Versioning
- •Extern Aliases
- •Attributes
- •Generics
- •Why generics?
- •Creating and consuming generics
- •Multiple type parameters
- •Constraints
- •Generic methods
- •Anonymous methods
- •Iterators
- •Lexical structure
- •Programs
- •Grammars
- •Lexical grammar
- •Syntactic grammar
- •Grammar ambiguities
- •Lexical analysis
- •Line terminators
- •Comments
- •White space
- •Tokens
- •Unicode escape sequences
- •Identifiers
- •Keywords
- •Literals
- •Boolean literals
- •Integer literals
- •Real literals
- •Character literals
- •String literals
- •The null literal
- •Operators and punctuators
- •Pre-processing directives
- •Conditional compilation symbols
- •Pre-processing expressions
- •Declaration directives
- •Conditional compilation directives
- •Diagnostic directives
- •Region control
- •Line directives
- •Pragma directives
- •Basic concepts
- •Application startup
- •Application termination
- •Declarations
- •Members
- •Namespace members
- •Struct members
- •Enumeration members
- •Class members
- •Interface members
- •Array members
- •Delegate members
- •Member access
- •Declared accessibility
- •Accessibility domains
- •Protected access for instance members
- •Accessibility constraints
- •Signatures and overloading
- •Scopes
- •Name hiding
- •Hiding through nesting
- •Hiding through inheritance
- •Namespace and type names
- •Unqualified name
- •Fully qualified names
- •Automatic memory management
- •Execution order
- •Types
- •Value types
- •The System.ValueType type
- •Default constructors
- •Struct types
- •Simple types
- •Integral types
- •Floating point types
- •The decimal type
- •The bool type
- •Enumeration types
- •Reference types
- •Class types
- •The object type
- •The string type
- •Interface types
- •Array types
- •Delegate types
- •Boxing and unboxing
- •Boxing conversions
- •Unboxing conversions
- •Variables
- •Variable categories
- •Static variables
- •Instance variables
- •Instance variables in classes
- •Instance variables in structs
- •Array elements
- •Value parameters
- •Reference parameters
- •Output parameters
- •Local variables
- •Default values
- •Definite assignment
- •Initially assigned variables
- •Initially unassigned variables
- •Precise rules for determining definite assignment
- •General rules for statements
- •Block statements, checked, and unchecked statements
- •Expression statements
- •Declaration statements
- •If statements
- •Switch statements
- •While statements
- •Do statements
- •For statements
- •Break, continue, and goto statements
- •Throw statements
- •Return statements
- •Try-catch statements
- •Try-finally statements
- •Try-catch-finally statements
- •Foreach statements
- •Using statements
- •Lock statements
- •General rules for simple expressions
- •General rules for expressions with embedded expressions
- •Invocation expressions and object creation expressions
- •Simple assignment expressions
- •&& expressions
- •|| expressions
- •! expressions
- •?: expressions
- •Anonymous method expressions
- •Yield statements
- •Variable references
- •Atomicity of variable references
- •Conversions
- •Implicit conversions
- •Identity conversion
- •Implicit numeric conversions
- •Implicit enumeration conversions
- •Implicit reference conversions
- •Boxing conversions
- •Implicit type parameter conversions
- •Implicit constant expression conversions
- •User-defined implicit conversions
- •Explicit conversions
- •Explicit numeric conversions
- •Explicit enumeration conversions
- •Explicit reference conversions
- •Unboxing conversions
- •User-defined explicit conversions
- •Standard conversions
- •Standard implicit conversions
- •Standard explicit conversions
- •User-defined conversions
- •Permitted user-defined conversions
- •Evaluation of user-defined conversions
- •User-defined implicit conversions
- •User-defined explicit conversions
- •Anonymous method conversions
- •Method group conversions
- •Expressions
- •Expression classifications
- •Values of expressions
- •Operators
- •Operator precedence and associativity
- •Operator overloading
- •Unary operator overload resolution
- •Binary operator overload resolution
- •Candidate user-defined operators
- •Numeric promotions
- •Unary numeric promotions
- •Binary numeric promotions
- •Member lookup
- •Base types
- •Function members
- •Argument lists
- •Overload resolution
- •Applicable function member
- •Better function member
- •Better conversion
- •Function member invocation
- •Invocations on boxed instances
- •Primary expressions
- •Literals
- •Simple names
- •Invariant meaning in blocks
- •Parenthesized expressions
- •Member access
- •Identical simple names and type names
- •Invocation expressions
- •Method invocations
- •Delegate invocations
- •Element access
- •Array access
- •Indexer access
- •This access
- •Base access
- •Postfix increment and decrement operators
- •The new operator
- •Object creation expressions
- •Array creation expressions
- •Delegate creation expressions
- •The typeof operator
- •The checked and unchecked operators
- •Default value expression
- •Anonymous methods
- •Anonymous method signatures
- •Anonymous method blocks
- •Outer variables
- •Captured outer variables
- •Instantiation of local variables
- •Anonymous method evaluation
- •Implementation example
- •Unary expressions
- •Unary plus operator
- •Unary minus operator
- •Logical negation operator
- •Bitwise complement operator
- •Prefix increment and decrement operators
- •Cast expressions
- •Arithmetic operators
- •Multiplication operator
- •Division operator
- •Remainder operator
- •Addition operator
- •Subtraction operator
- •Shift operators
- •Relational and type-testing operators
- •Integer comparison operators
- •Floating-point comparison operators
- •Decimal comparison operators
- •Boolean equality operators
- •Enumeration comparison operators
- •Reference type equality operators
- •String equality operators
- •Delegate equality operators
- •The is operator
- •The as operator
- •Logical operators
- •Integer logical operators
- •Enumeration logical operators
- •Boolean logical operators
- •Conditional logical operators
- •Boolean conditional logical operators
- •User-defined conditional logical operators
- •Conditional operator
- •Assignment operators
- •Simple assignment
- •Compound assignment
- •Event assignment
- •Expression
- •Constant expressions
- •Boolean expressions
- •Statements
- •End points and reachability
- •Blocks
- •Statement lists
- •The empty statement
- •Labeled statements
- •Declaration statements
- •Local variable declarations
- •Local constant declarations
- •Expression statements
- •Selection statements
- •The if statement
- •The switch statement
- •Iteration statements
- •The while statement
- •The do statement
- •The for statement
- •The foreach statement
- •Jump statements
- •The break statement
- •The continue statement
- •The goto statement
- •The return statement
- •The throw statement
- •The try statement
- •The checked and unchecked statements
- •The lock statement
- •The using statement
- •The yield statement
- •Namespaces
- •Compilation units
- •Namespace declarations
- •Extern alias directives
- •Using directives
- •Using alias directives
- •Using namespace directives
- •Namespace members
- •Type declarations
- •Qualified alias member
- •Classes
- •Class declarations
- •Class modifiers
- •Abstract classes
- •Sealed classes
- •Static classes
- •Class base specification
- •Base classes
- •Interface implementations
- •Class body
- •Partial declarations
- •Class members
- •Inheritance
- •The new modifier
- •Access modifiers
- •Constituent types
- •Static and instance members
- •Nested types
- •Fully qualified name
- •Declared accessibility
- •Hiding
- •this access
- •Reserved member names
- •Member names reserved for properties
- •Member names reserved for events
- •Member names reserved for indexers
- •Member names reserved for destructors
- •Constants
- •Fields
- •Static and instance fields
- •Readonly fields
- •Using static readonly fields for constants
- •Versioning of constants and static readonly fields
- •Volatile fields
- •Field initialization
- •Variable initializers
- •Static field initialization
- •Instance field initialization
- •Methods
- •Method parameters
- •Value parameters
- •Reference parameters
- •Output parameters
- •Parameter arrays
- •Static and instance methods
- •Virtual methods
- •Override methods
- •Sealed methods
- •Abstract methods
- •External methods
- •Method body
- •Method overloading
- •Properties
- •Static and instance properties
- •Accessors
- •Virtual, sealed, override, and abstract accessors
- •Events
- •Field-like events
- •Event accessors
- •Static and instance events
- •Virtual, sealed, override, and abstract accessors
- •Indexers
- •Indexer overloading
- •Operators
- •Unary operators
- •Binary operators
- •Conversion operators
- •Instance constructors
- •Constructor initializers
- •Instance variable initializers
- •Constructor execution
- •Default constructors
- •Private constructors
- •Optional instance constructor parameters
- •Static constructors
- •Destructors
- •Structs
- •Struct declarations
- •Struct modifiers
- •Struct interfaces
- •Struct body
- •Struct members
- •Class and struct differences
- •Value semantics
- •Inheritance
- •Assignment
- •Default values
- •Boxing and unboxing
- •Meaning of this
- •Field initializers
- •Constructors
- •Destructors
- •Static constructors
- •Struct examples
- •Database integer type
- •Database boolean type
- •Arrays
- •Array types
- •The System.Array type
- •Array creation
- •Array element access
- •Array members
- •Array covariance
- •Arrays and the generic IList interface
- •Array initializers
- •Interfaces
- •Interface declarations
- •Interface modifiers
- •Base interfaces
- •Interface body
- •Interface members
- •Interface methods
- •Interface properties
- •Interface events
- •Interface indexers
- •Interface member access
- •Fully qualified interface member names
- •Interface implementations
- •Explicit interface member implementations
- •Interface mapping
- •Interface implementation inheritance
- •Interface re-implementation
- •Abstract classes and interfaces
- •Enums
- •Enum declarations
- •Enum modifiers
- •Enum members
- •The System.Enum type
- •Enum values and operations
- •Delegates
- •Delegate declarations
- •Delegate instantiation
- •Delegate invocation
- •Exceptions
- •Causes of exceptions
- •The System.Exception class
- •How exceptions are handled
- •Common Exception Classes
- •Attributes
- •Attribute classes
- •Attribute usage
- •Positional and named parameters
- •Attribute parameter types
- •Attribute specification
- •Attribute instances
- •Compilation of an attribute
- •Run-time retrieval of an attribute instance
- •Reserved attributes
- •The AttributeUsage attribute
- •The Conditional attribute
- •Conditional Methods
- •Conditional Attribute Classes
- •The Obsolete attribute
- •Unsafe code
- •Unsafe contexts
- •Pointer types
- •Fixed and moveable variables
- •Pointer conversions
- •Pointers in expressions
- •Pointer indirection
- •Pointer member access
- •Pointer element access
- •The address-of operator
- •Pointer increment and decrement
- •Pointer arithmetic
- •Pointer comparison
- •The sizeof operator
- •The fixed statement
- •Stack allocation
- •Dynamic memory allocation
- •Generics
- •Generic class declarations
- •Type parameters
- •The instance type
- •Members of generic classes
- •Static fields in generic classes
- •Static constructors in generic classes
- •Accessing protected members
- •Overloading in generic classes
- •Parameter array methods and type parameters
- •Overriding and generic classes
- •Operators in generic classes
- •Nested types in generic classes
- •Generic struct declarations
- •Generic interface declarations
- •Uniqueness of implemented interfaces
- •Explicit interface member implementations
- •Generic delegate declarations
- •Constructed types
- •Type arguments
- •Open and closed types
- •Base classes and interfaces of a constructed type
- •Members of a constructed type
- •Accessibility of a constructed type
- •Conversions
- •Using alias directives
- •Generic methods
- •Generic method signatures
- •Virtual generic methods
- •Calling generic methods
- •Inference of type arguments
- •Using a generic method with a delegate
- •Constraints
- •Satisfying constraints
- •Member lookup on type parameters
- •Type parameters and boxing
- •Conversions involving type parameters
- •Iterators
- •Iterator blocks
- •Enumerator interfaces
- •Enumerable interfaces
- •Yield type
- •This access
- •Enumerator objects
- •The MoveNext method
- •The Current property
- •The Dispose method
- •Enumerable objects
- •The GetEnumerator method
- •Implementation example
- •Lexical grammar
- •Line terminators
- •White space
- •Comments
- •Unicode character escape sequences
- •Identifiers
- •Keywords
- •Literals
- •Operators and punctuators
- •Pre-processing directives
- •Syntactic grammar
- •Basic concepts
- •Types
- •Expressions
- •Statements
- •Classes
- •Structs
- •Arrays
- •Interfaces
- •Enums
- •Delegates
- •Attributes
- •Generics
- •Grammar extensions for unsafe code
- •Undefined behavior
- •Implementation-defined behavior
- •Unspecified behavior
- •Other Issues
- •Capitalization styles
- •Pascal casing
- •Camel casing
- •All uppercase
- •Capitalization summary
- •Word choice
- •Namespaces
- •Classes
- •Interfaces
- •Enums
- •Static fields
- •Parameters
- •Methods
- •Properties
- •Events
- •Case sensitivity
- •Avoiding type name confusion
- •Documentation Comments
- •Introduction
- •Recommended tags
- •<code>
- •<example>
- •<exception>
- •<list>
- •<para>
- •<param>
- •<paramref>
- •<permission>
- •<remarks>
- •<returns>
- •<seealso>
- •<summary>
- •<value>
- •Processing the documentation file
- •ID string format
- •ID string examples
- •An example
- •C# source code
- •Resulting XML
C# LANGUAGE SPECIFICATION
1 defines an attribute class named HelpAttribute, or Help for short, that has one positional parameter 2 (string url) and one named parameter (string Topic). Positional parameters are defined by the
3formal parameters for public instance constructors of the attribute class, and named parameters are defined
4by public non-static read-write fields and properties of the attribute class.
5The example
6[Help("http://www.mycompany.com/…/Class1.htm")]
7public class Class1
8{
9 |
[Help("http://www.mycompany.com/…/Class1.htm", Topic = "F")] |
10public void F() {}
11}
12shows several uses of the attribute Help.
13Attribute information for a given program element can be retrieved at run-time by using reflection support.
14The example
15using System;
16class Test
17{
18 |
static void Main() { |
19 |
Type type = typeof(Class1); |
20 |
object[] arr = type.GetCustomAttributes( |
21 |
typeof(HelpAttribute), true); |
22 |
if (arr.Length == 0) |
23 |
Console.WriteLine("Class1 has no Help attribute."); |
24 |
else { |
25 |
HelpAttribute ha = (HelpAttribute) arr[0]; |
26 |
Console.WriteLine("Url = {0}, Topic = {1}", ha.Url, ha.Topic); |
27 |
} |
28}
29}
30checks to see if Class1 has a Help attribute, and writes out the associated Url and Topic values if the
31attribute is present.
328.16 Generics
33C# allows classes, structs, interfaces, and methods to be parameterized by the types of data they store and
34manipulate. This feature is really a set of features known collectively as generics. C# generics will
35immediately be familiar to users of generics in Eiffel or Ada, or to users of templates in C++.
36Many common classes and structs can be parameterized by the types of data being stored and manipulated—
37these are called generic class declarations and generic struct declarations, respectively. Similarly, many
38interfaces define contracts that can be parameterized by the types of data they handle—these are called
39generic interface declarations. In order to implement “generic algorithms,” methods can also be
40parameterized by type; such methods are known as generic methods.
418.16.1 Why generics?
42Without generics, programmers can store data of any type in variables of the base type object. To
43illustrate, let’s create a simple Stack type with two actions, “Push” and “Pop”. The Stack class stores its
44data in an array of object, and the Push and Pop methods use the object type to accept and return data,
45respectively:
46public class Stack
47{
48 |
private object[] items = new object[100]; |
49 |
public void Push(object data) {…} |
50public object Pop() {…}
51}
52We can then push a value of any type, such as a Customer type, for example, onto the stack. However,
53when we wanted to retrieve that value, we would need to explicitly cast the result of the Pop method, an
52
Chapter 8 Language overview
1object, into a Customer type, which is tedious to write, and carries a performance penalty for run-time
2type checking:
3Stack s = new Stack();
4s.Push(new Customer());
5Customer c = (Customer)s.Pop();
6If we pass to the Push method a value type, such as an int, it will automatically be boxed. Similarly, if we
7want to retrieve an int from the stack, we would need to explicitly unbox the object type we obtain from
8the Pop method:
9Stack s = new Stack();
10s.Push(3);
11int i = (int)s.Pop();
12Such boxing and unboxing operations can impact performance.
13Furthermore, in the implementation shown, it is not possible to enforce the kind of data placed in the stack.
14Indeed, we could create a stack and push a Customer type onto it. However, later, we could use the same
15stack and try to pop data off of it and cast it into an incompatible type:
16Stack s = new Stack();
17s.Push(new Customer());
18Employee e = (Employee)s.Pop(); // runtime error
19While the code above is an improper use of the Stack class we presumably intended to implement, and
20should be a compile-time error, it is actually valid code. However, at run-time, the application will fail
21because we have performed an invalid cast operation.
228.16.2 Creating and consuming generics
23Generics provide a facility for creating high-performance data structures that are specialized by the compiler
24and/or execution engine based on the types that they use. These so-called generic type declarations are
25created so that their internal algorithms remain the same, yet the types of their external interface and internal
26data can vary based on user preference.
27In order to minimize the learning curve for developers, generics are used in much the same way as C++
28templates. Programmers can create classes and structures just as they normally have, and by using the angle
29bracket notation (< and >) they can specify type parameters. When the generic class declaration is used, each
30type parameter shall be replaced by a type argument that the user of the class supplies.
31In the example below, we create a Stack generic class declaration where we specify a type parameter,
32called ItemType, declared in angle brackets after the declaration. Rather than forcing conversions to and
33from object, instances of the generic Stack class will accept the type for which they are created and store
34data of that type without conversion. The type parameter ItemType acts as a placeholder until an actual
35type is specified at use. Note that ItemType is used as the element type for the internal items array, the type
36for the parameter to the Push method, and the return type for the Pop method:
37public class Stack<ItemType>
38{
39 |
private ItemType[] items = new ItemType[100]; |
40 |
public void Push(ItemType data) {…} |
41public ItemType Pop() {…}
42}
43When we use the generic class declaration Stack, as in the short example below, we can specify the actual
44type to be used by the generic class. In this case, we instruct the Stack to use an int type by specifying it
45as a type argument using the angle brackets after the name:
46Stack<int> s = new Stack<int>();
47s.Push(3);
48int x = s.Pop();
49In so doing, we have created a new constructed type, Stack<int>, for which every ItemType inside the
50declaration of Stack is replaced with the supplied type argument int. Indeed, when we create our new
51instance of Stack<int>, the native storage of the items array is now an int[] rather than object[],
53
C# LANGUAGE SPECIFICATION
1providing substantial storage efficiency. Additionally, we have eliminated the boxing penalty associated
2with pushing an int onto the stack. Further, when we pop an item off the stack, we no longer need to
3explicitly cast it to the appropriate type because this particular kind of Stack class natively stores an int in
4its data structure.
5If we wanted to store items other than an int into a Stack, we would have to create a different constructed
6type from Stack, specifying a new type argument. Suppose we had a simple Customer type and we
7wanted to use a Stack to store it. To do so, we simply use the Customer class as the type argument to
8Stack and easily reuse our code:
9Stack<Customer> s = new Stack<Customer>();
10s.Push(new Customer());
11Customer c = s.Pop();
12Of course, once we’ve created a Stack with a Customer type as its type argument, we are now limited to
13storing only Customer objects (or objects of a class derived from Customer). Generics provide strong
14typing, meaning we can no longer improperly store an integer into the stack, like so:
15Stack<Customer> s = new Stack<Customer>();
16s.Push(new Customer());
17 |
s.Push(3); |
// |
compile-time error |
18 |
Customer c = s.Pop(); |
// |
no cast required |
198.16.3 Multiple type parameters
20Generic type declarations can have any number of type parameters. In our Stack example, we used only
21one type parameter. Suppose we created a simple Dictionary generic class declaration that stored values
22alongside keys. We could define a generic version of a Dictionary by declaring two type parameters,
23separated by commas within the angle brackets of the declaration:
24public class Dictionary<KeyType, ElementType>
25{
26 |
public void Add(KeyType key, ElementType val) {…} |
27public ElementType this[KeyType key] {…}
28}
29When we use Dictionary, we need to supply two type arguments within the angle brackets. Then when
30we call the Add function or use the indexer, the compiler checks that we supplied the right types:
31Dictionary<string, Customer> dict = new Dictionary<string, Customer>();
32dict.Add("Peter", new Customer());
33Customer c = dict["Peter"];
348.16.4 Constraints
35In many cases, we will do more than store data based on a given type parameter. Often, we will also want to
36use members of the type parameter to execute statements within our generic type declaration.
37For example, suppose in the Add method of our Dictionary we wanted to compare items using the
38CompareTo method of the supplied key, like so:
39public class Dictionary<KeyType, ElementType>
40{
41 |
public void Add(KeyType key, ElementType val) |
42 |
{ |
43 |
… |
44 |
if (key.CompareTo(x) < 0) {…} // compile-time error |
45 |
… |
46}
47}
48Unfortunately, at compile-time the type parameter KeyType is, as expected, generic. As written, the
49compiler will assume that only the operations available to object, such as ToString, are available on the
50variable key of type KeyType. As a result, the compiler will display an error because the CompareTo
51method would not be found. However, we can cast the key variable to a type that does contain a
52CompareTo method, such as an IComparable interface, allowing the program to compile:
54
Chapter 8 Language overview
1public class Dictionary<KeyType, ElementType>
2{
3 |
public void Add(KeyType key, ElementType val) |
4 |
{ |
5 |
… |
6 |
if (((IComparable)key).CompareTo(x) < 0) {…} |
7 |
… |
8}
9}
10However, if we now construct a type from Dictionary and supply a type argument which does not
11implement IComparable, we will encounter a run-time error, specifically an InvalidCastException.
12Since one of the objectives of generics is to provide strong typing and to reduce the need for casts, a more
13elegant solution is needed.
14We can supply an optional list of constraints for each type parameter. A constraint indicates a requirement
15that a type shall fulfill in order to be accepted as a type argument. (For example, it might have to implement
16a given interface or be derived from a given base class.) A constraint is declared using the word where,
17followed by a type parameter and colon (:), followed by a comma-separated list of constraints, which can
18include a class type, interface types, other type parameters, the reference type constraint “class”, the value
19type constraint “struct”, and the constructor constraint “new()”.
20In order to satisfy our need to use the CompareTo method inside Dictionary, we can impose a constraint
21on KeyType, requiring any type passed as the first argument to Dictionary to implement IComparable,
22like so:
23public class Dictionary<KeyType, ElementType> where KeyType: IComparable
24{
25 |
public void Add(KeyType key, ElementType val) |
26 |
{ |
27 |
… |
28 |
if (key.CompareTo(x) < 0) {…} |
29 |
… |
30}
31}
32When compiled, this code will now be checked to ensure that each time we construct a Dictionary type
33we are passing a first type argument that implements IComparable. Further, we no longer have to
34explicitly cast variable key to IComparable before calling the CompareTo method.
35Constraints are most useful when they are used in the context of defining a framework, i.e. a collection of
36related classes, where it is advantageous to ensure that a number of types support some common signatures
37and/or base types. Constraints can be used to help define “generic algorithms” that plug together
38functionality provided by different types. This can also be achieved by subclassing and runtime
39polymorphism, but static, constrained polymorphism can, in many cases, result in more efficient code, more
40flexible specifications of generic algorithms, and more errors being caught at compile-time rather than run-
41time. However, constraints need to be used with care and taste. Types that do not implement the constraints
42will not easily be usable in conjunction with generic code.
43For any given type parameter, we can specify any number of interfaces and type parameters as constraints,
44but no more than one class. Each constrained type parameter has a separate where clause. In the example
45below, the KeyType type parameter has two interface constraints, while the ElementType type parameter
46has one class constraint:
47public class Dictionary<KeyType, ElementType >
48 |
where KeyType: IComparable, IEnumerable |
49where ElementType: Customer
50{
51 |
public void Add(KeyType key, ElementType val) |
52 |
{ |
53 |
… |
55