Copyright Microsoft Corporation 1999-2003. All Rights Reserved.
C# Version 2.0 Specification July 2003
Table of Contents
19. Introduction to C# 2.0---1
19.1 Generics---1
19.1.1 Why generics? ---1
19.1.2 Creating and using generics---2
19.1.3 Generic type instantiations ---3
19.1.4 Constraints---3
19.1.5 Generic methods ---5
19.2 Anonymous methods---5
19.2.1 Method group conversions ---8
19.3 Iterators---8
19.4 Partial types ---11
20. Generics ---13
20.1 Generic class declarations---13
20.1.1 Type parameters ---13
20.1.2 The instance type ---14
20.1.3 Base specification ---15
20.1.4 Members of generic classes---15
20.1.5 Static fields in generic classes ---16
20.1.6 Static constructors in generic classes ---16
20.1.7 Accessing protected members ---17
20.1.8 Overloading in generic classes ---17
20.1.9 Parameter array methods and type parameters ---18
20.1.10 Overriding and generic classes ---19
20.1.11 Operators in generic classes ---19
20.1.12 Nested types in generic classes--20
20.1.13 Application entry point ---21
20.2 Generic struct declarations ---21
20.3 Generic interface declarations---21
20.3.1 Uniqueness of implemented interfaces ---22
20.3.2 Explicit interface member implementations---22
20.4 Generic delegate declarations ---23
20.5 Constructed types ---23
20.5.1 Type arguments ---24
20.5.2 Open and closed types---25
20.5.3 Base classes and interfaces of a constructed type---25
20.5.4 Members of a constructed type---25
20.5.5 Accessibility of a constructed type ---26
20.5.6 Conversions ---27
20.5.7 The System.Nullable
20.5.8 Using alias directives ---27
20.5.9 Attributes---28
20.6 Generic methods ---28
20.6.1 Generic method signatures ---29
20.6.2 Virtual generic methods ---30
20.6.3 Calling generic methods---30
20.6.4 Inference of type arguments ---31
20.6.5 Grammar ambiguities---32
20.6.6 Using a generic method with a delegate---32
C# 2.0 SPECIFICATION
iv Copyright Microsoft Corporation 1999-2003. All Rights Reserved.
20.6.7 No generic properties, events, indexers, or operators ---33
20.7 Constraints ---33
20.7.1 Satisfying constraints ---35
20.7.2 Member lookup on type parameters---36
20.7.3 Type parameters and boxing ---36
20.7.4 Conversions involving type parameters ---37
20.8 Expressions and Statements---39
20.8.1 Default value expression ---39
20.8.2 Object creation expressions---39
20.8.3 The typeof operator--39
20.8.4 Reference equality operators ---40
20.8.5 The is operator---40
20.8.6 The as operator ---40
20.8.7 Exception statements --41
20.8.8 The lock statement --41
20.8.9 The using statement --41
20.8.10 The foreach statement --41
20.9 Revised lookup rules --42
20.9.1 Namespace and type names--42
20.9.2 Member lookup--43
20.9.3 Simple names--44
20.9.4 Member access --45
20.9.5 Method invocations--47
20.9.6 Delegate creation expressions --48
20.10 Right-shift grammar changes --49
21. Anonymous methods--51
21.1 Anonymous method expressions--51
21.2 Anonymous method signatures --51
21.3 Anonymous method conversions --51
21.3.1 Delegate creation expression--53
21.4 Anonymous method blocks --53
21.5 Outer variables --53
21.5.1 Captured outer variables--54
21.5.2 Instantiation of local variables--54
21.6 Anonymous method evaluation --56
21.7 Delegate instance equality --57
21.8 Definite assignment--57
21.9 Method group conversions --58
21.10 Implementation example --59
22. Iterators --63
22.1 Iterator blocks --63
22.1.1 Enumerator interfaces --63
22.1.2 Enumerable interfaces--63
22.1.3 Yield type --63
22.1.4 This access--64
22.2 Enumerator objects--64
22.2.1 The MoveNext method --64
22.2.2 The Current property--65
22.2.3 The Dispose method --66
Table of Contents
Copyright Microsoft Corporation 1999-2003. All Rights Reserved. v
22.3 Enumerable objects --66
22.3.1 The GetEnumerator method --66
22.4 The yield statement --67
22.4.1 Definite assignment --68
22.5 Implementation example --68
23. Partial Types--73
23.1 Partial declarations --73
23.1.1 Attributes--73
23.1.2 Modifiers --74
23.1.3 Type parameters and constraints--74
23.1.4 Base class --74
23.1.5 Base interfaces--75
23.1.6 Members--75
23.2 Name binding--76
Chapter 19 Introduction to C# 2.0-
Copyright Microsoft Corporation 1999-2003. All Rights Reserved. 1
19. Introduction to C# 2.0
C# 2.0 introduces several language extensions, the most important of which are Generics, Anonymous Methods,
Iterators, and Partial Types.
• Generics permit classes, structs, interfaces, delegates, and methods to be parameterized by the types of data
they store and manipulate. Generics are useful because they provide stronger compile-time type checking,
require fewer explicit conversions between data types, and reduce the need for boxing operations and runtime
type checks.
• Anonymous methods allow code blocks to be written “in-line” where delegate values are expected.
Anonymous methods are similar to lambda functions in the Lisp programming language. C# 2.0 supports
the creation of “closures” where anonymous methods access surrounding local variables and parameters.
• Iterators are methods that incrementally compute and yield a sequence of values. Iterators make it easy for a
type to specify how the foreach statement will iterate over its elements.
• Partial types allow classes, structs, and interfaces to be broken into multiple pieces stored in different source
files for easier development and maintenance. Additionally, partial types allow separation of machinegenerated
and user-written parts of types so that it is easier to augment code generated by a tool.
This chapter gives an introduction to these new features. Following the introduction are four chapters that
provide a complete technical specification of the features.
The language extensions in C# 2.0 were designed to ensure maximum compatibility with existing code. For
example, even though C# 2.0 gives special meaning to the words where, yield, and partial in certain
contexts, these words can still be used as identifiers. Indeed, C# 2.0 adds no new keywords as such keywords
could conflict with identifiers in existing code.
19.1 Generics
Generics permit classes, structs, interfaces, delegates, and methods to be parameterized by the types of data they
store and manipulate. C# generics will be immediately familiar to users of generics in Eiffel or Ada, or to users
of C++ templates, though they do not suffer many of the complications of the latter.
19.1.1 Why generics?
Without generics, general purpose data structures can use type object to store data of any type. For example,
the following simple Stack class stores its data in an object array, and its two methods, Push and Pop, use
object to accept and return data, respectively:
public class Stack
{
object[] items;
int count;
public void Push(object item) {...}
public object Pop() {...}
}
While the use of type object makes the Stack class very flexible, it is not without drawbacks. For example, it
is possible to push a value of any type, such a Customer instance, onto a stack. However, when a value is
C# 2.0 SPECIFICATION
2 Copyright Microsoft Corporation 1999-2003. All Rights Reserved.
retrieved, the result of the Pop method must explicitly be cast back to the appropriate type, which is tedious to
write and carries a performance penalty for run-time type checking:
Stack stack = new Stack();
stack.Push(new Customer());
Customer c = (Customer)stack.Pop();
If a value of a value type, such as an int, is passed to the Push method, it is automatically boxed. When the
int is later retrieved, it must be unboxed with an explicit type cast:
Stack stack = new Stack();
stack.Push(3);
int i = (int)stack.Pop();
Such boxing and unboxing operations add performance overhead since they involve dynamic memory
allocations and run-time type checks.
A further issue with the Stack class is that it is not possible to enforce the kind of data placed on a stack.
Indeed, a Customer instance can be pushed on a stack and then accidentally cast it to the wrong type after it is
retrieved:
Stack stack = new Stack();
stack.Push(new Customer());
string s = (string)stack.Pop();
While the code above is an improper use of the Stack class, the code is technically speaking correct and a
compile-time error is not reported. The problem does not become apparent until the code is executed, at which
point an InvalidCastException is thrown.
The Stack class would clearly benefit from the ability to specify its element type. With generics, that becomes
possible.
19.1.2 Creating and using generics
Generics provide a facility for creating types that have type parameters. The example below declares a generic
Stack class with a type parameter T. The type parameter is specified in < and > delimiters after the class name.
Rather than forcing conversions to and from object, instances of Stack
created and store data of that type without conversion. The type parameter T acts as a placeholder until an actual
type is specified at use. Note that T is used as the element type for the internal items array, the type for the
parameter to the Push method, and the return type for the Pop method:
public class Stack
{
T[] items;
int count;
public void Push(T item) {...}
public T Pop() {...}
}
When the generic class Stack
example, int is given as the type argument for T:
Stack
stack.Push(3);
int x = stack.Pop();
The Stack
with the type argument int. When an instance of Stack
is an int[] rather than object[], providing substantial storage efficiency compared to the non-generic
Stack. Likewise, the Push and Pop methods of a Stack
19 Introduction to C# 2.0
Copyright Microsoft Corporation 1999-2003. All Rights Reserved. 3
time error to push values of other types onto the stack, and eliminating the need to explicitly cast values back to
their original type when they’re retrieved.
Generics provide strong typing, meaning for example that it is an error to push an int onto a stack of
Customer objects. Just as a Stack
restricted to Customer objects, and the compiler will report errors on the last two lines of the following
example:
Stack
stack.Push(new Customer());
Customer c = stack.Pop();
stack.Push(3); // Type mismatch error
int x = stack.Pop(); // Type mismatch error
Generic type declarations may have any number of type parameters. The Stack
one type parameter, but a generic Dictionary class might have two type parameters, one for the type of the
keys and one for the type of the values:
public class Dictionary
{
public void Add(K key, V value) {...}
public V this[K key] {...}
}
When Dictionary
Dictionary
dict.Add("Peter", new Customer());
Customer c = dict["Peter"];
19.1.3 Generic type instantiations
Similar to a non-generic type, the compiled representation of a generic type is intermediate language (IL)
instructions and metadata. The representation of the generic type of course also encodes the existence and use of
type parameters.
The first time an application creates an instance of a constructed generic type, such as Stack
(JIT) compiler of the .NET Common Language Runtime converts the generic IL and metadata to native
code, substituting actual types for type parameters in the process. Subsequent references to that constructed
generic type then use the same native code. The process of creating a specific constructed type from a generic
type is known as a generic type instantiation.
The .NET Common Language Runtime creates a specialized copy of the native code for each generic type
instantiation with a value type, but shares a single copy of the native code for all reference types (since, at the
native code level, references are just pointers with the same representation).
19.1.4 Constraints
Commonly, a generic class will do more than just store data based on a type parameter. Often, the generic class
will want to invoke methods on objects whose type is given by a type parameter. For example, an Add method
in a Dictionary
public class Dictionary
{
public void Add(K key, V value)
{
...
C# 2.0 SPECIFICATION
4 Copyright Microsoft Corporation 1999-2003. All Rights Reserved.
if (key.CompareTo(x) < 0) {...} // Error, no CompareTo method
...
}
}
Since the type argument specified for K could be any type, the only members that can be assumed to exist on the
key parameter are those declared by type object, such as Equals, GetHashCode, and ToString; a compiletime
error therefore occurs in the example above. It is of course possible to cast the key parameter to a type that
contains a CompareTo method. For example, the key parameter could be cast to IComparable:
public class Dictionary
{
public void Add(K key, V value)
{
...
if (((IComparable)key).CompareTo(x) < 0) {...}
...
}
}
While this solution works, it requires a dynamic type check at run-time, which adds overhead. It furthermore
defers error reporting to run-time, throwing an InvalidCastException if a key doesn’t implement
IComparable.
To provide stronger compile-time type checking and reduce type casts, C# permits an optional list of constraints
to be supplied for each type parameter. A type parameter constraint specifies a requirement that a type must
fulfill in order to be used as an argument for that type parameter. Constraints are declared using the word
where, followed by the name of a type parameter, followed by a list of class or interface types, or the
constructor constraint new().
In order for the Dictionary
declaration can specify a constraint for the type parameter K:
public class Dictionary
{
public void Add(K key, V value)
{
...
if (key.CompareTo(x) < 0) {...}
...
}
}
Given this declaration the compiler will ensure that any type argument supplied for K is a type that implements
IComparable. Furthermore, it is no longer necessary to explicitly cast the key parameter to IComparable
before calling the CompareTo method; all members of a type given as a constraint for a type parameter are
directly available on values of that type parameter type.
For a given type parameter, it is possible to specify any number of interfaces as constraints, but no more than
one class. Each constrained type parameter has a separate where clause. In the example below, the type
parameter K has two interface constraints, while the type parameter E has a class constraint and a constructor
constraint:
public class EntityTable
where K: IComparable
where E: Entity, new()
{
public void Add(K key, E entity)
{
...
Chapter 19 Introduction to C# 2.0
Copyright Microsoft Corporation 1999-2003. All Rights Reserved. 5
if (key.CompareTo(x) < 0) {...}
...
}
}
The constructor constraint, new(), in the example above ensures that a type used as a type argument for E has a
public, parameterless constructor, and it permits the generic class to use new E() to create instances of that type.
Type parameter constrains should be used with care. While they provide stronger compile-time type checking
and in some cases improve performance, they also restrict the possible uses of a generic type. For example, a
generic class List
compare items. However, doing so would preclude use of List
IComparable, even if the Sort method is never actually called in those cases.
19.1.5 Generic methods
In some cases a type parameter is not needed for an entire class, but only inside a particular method. Often, this
occurs when creating a method that takes a generic type as a parameter. For example, when using the Stack
class described earlier, a common pattern might be to push multiple values in a row, and it might be convenient
to write a method that does so in a single call. For a particular constructed type, such as Stack
method would look like this:
void PushMultiple(Stack
foreach (int value in values) stack.Push(value);
}
This method can be used to push multiple int values onto a Stack
Stack
PushMultiple(stack, 1, 2, 3, 4);
However, the method above only works with the particular constructed type Stack
any Stack
parameters specified in < and > delimiters after the method name. The type parameters can be used within the
parameter list, return type, and body of the method. A generic PushMultiple method would look like this:
void PushMultiple
foreach (T value in values) stack.Push(value);
}
Using this generic method, it is possible to push multiple items onto any Stack
method, type arguments are given in angle brackets in the method invocation. For example:
Stack
PushMultiple
This generic PushMultiple method is more reusable than the previous version, since it works on any
Stack
to the method. In many cases, however, the compiler can deduce the correct type argument from the other
arguments passed to the method, using a process called type inferencing. In the example above, since the first
regular argument is of type Stack
reason that the type parameter must be int. Thus, the generic PushMultiple method can be called without
specifying the type parameter:
Stack
PushMultiple(stack, 1, 2, 3, 4);
19.2 Anonymous methods
Event handlers and other callbacks are often invoked exclusively through delegates and never directly. Even so,
it has thus far been necessary to place the code of event handlers and callbacks in distinct methods to which
C# 2.0 SPECIFICATION
6 Copyright Microsoft Corporation 1999-2003. All Rights Reserved.
delegates are explictly created. In contrast, anonymous methods allow the code associated with a delegate to be
written “in-line” where the delegate is used, conveniently tying the code directly to the delegate instance.
Besides this convenience, anonymous methods have shared access to the local state of the containing function
member. To achieve the same state sharing using named methods requires “lifting” local variables into fields in
instances of manually authored helper classes.
The following example shows a simple input form that contains a list box, a text box, and a button. When the
button is clicked, an item containing the text in the text box is added to the list box.
class InputForm: Form
{
ListBox listBox;
TextBox textBox;
Button addButton;
public MyForm() {
listBox = new ListBox(...);
textBox = new TextBox(...);
addButton = new Button(...);
addButton.Click += new EventHandler(AddClick);
}
void AddClick(object sender, EventArgs e) {
listBox.Items.Add(textBox.Text);
}
}
Even though only a single statement is executed in response to the button’s Click event, that statement must be
extracted into a separate method with a full parameter list, and an EventHandler delegate referencing that
method must be manually created. Using an anonymous method, the event handling code becomes significantly
more succinct:
class InputForm: Form
{
ListBox listBox;
TextBox textBox;
Button addButton;
public MyForm() {
listBox = new ListBox(...);
textBox = new TextBox(...);
addButton = new Button(...);
addButton.Click += delegate {
listBox.Items.Add(textBox.Text);
};
}
}
An anonymous method consists of the keyword delegate, an optional parameter list, and a statement list
enclosed in { and } delimiters. The anonymous method in the previous example doesn’t use the parameters
supplied by the delegate, and it can therefore omit the parameter list. To gain access to the parameters, the
anonymous method can include a parameter list:
addButton.Click += delegate(object sender, EventArgs e) {
MessageBox.Show(((Button)sender).Text);
};
In the previous examples, an implicit conversion occurs from the anonymous method to the EventHandler
delegate type (the type of the Click event). This implict conversion is possible because the parameter list and
return type of the delegate type are compatible with the anonymous method. The exact rules for compatibility
are as follows:
Chapter 19 Introduction to C# 2.0
Copyright Microsoft Corporation 1999-2003. All Rights Reserved. 7
• The parameter list of a delegate is compatible with an anonymous method if one of the following is true:
o The anonymous method has no parameter list and the delegate has no out parameters.
o The anonymous method includes a parameter list that exactly matches the delegate’s parameters in
number, types, and modifiers.
• The return type of a delegate is compatible with an anonymous method if one of the following is true:
o The delegate’s return type is void and the anonymous method has no return statements or only
return statements with no expression.
o The delegate’s return type is not void and the expressions associated with all return statements in the
anonymous method can be implicitly converted to the return type of the delegate.
Both the parameter list and the return type of a delegate must be compatible with an anonymous method before
an implicit conversion to that delegate type can occur.
The following example uses anonymous methods to write functions “in-line.” The anonymous methods are
passed as parameters of a Function delegate type.
using System;
delegate double Function(double x);
class Test
{
static double[] Apply(double[] a, Function f) {
double[] result = new double[a.Length];
for (int i = 0; i < a.Length; i++) result[i] = f(a[i]);
return result;
}
static double[] MultiplyAllBy(double[] a, double factor) {
return Apply(a, delegate(double x) { return x * factor; });
}
static void Main() {
double[] a = {0.0, 0.5, 1.0};
double[] squares = Apply(a, delegate(double x) { return x * x; });
double[] doubles = MultiplyAllBy(a, 2.0);
}
}
The Apply method applies a given Function to the elements of a double[], returning a double[] with the
results. In the Main method, the second parameter passed to Apply is an anonymous method that is compatible
with the Function delegate type. The anonymous method simply returns the square of its argument, and thus
the result of that Apply invocation is a double[] containing the squares of the values in a.
The MultiplyAllBy method returns a double[] created by multiplying each of the values in the argument
array a by a given factor. In order to produce its result, MultiplyAllBy invokes the Apply method, passing
an anonymous method that multiplies the argument x by factor.
Local variables and parameters whose scope contains an anonymous method are called outer variables of the
anonymous method. In the MultiplyAllBy method, a and factor are outer variables of the anonymous
method passed to Apply, and because the anonymous method references factor, factor is said to have been
captured by the anonymous method. Ordinarily, the lifetime of a local variable is limited to execution of the
block or statement with which it is associated. However, the lifetime of a captured outer variable is extended at
least until the delegate referring to the anonymous method becomes eligible for garbage collection.
C# 2.0 SPECIFICATION
8 Copyright Microsoft Corporation 1999-2003. All Rights Reserved.
19.2.1 Method group conversions
As described in the previous section, an anonymous method can be implicitly converted to a compatible
delegate type. C# 2.0 permits this same type of conversion for a method group, allowing explicit delegate
instantiations to be omitted in almost all cases. For example, the statements
addButton.Click += new EventHandler(AddClick);
Apply(a, new Function(Math.Sin));
can instead be written
addButton.Click += AddClick;
Apply(a, Math.Sin);
When the shorter form is used, the compiler automatically infers which delegate type to instantiate, but the
effects are otherwise the same as the longer form.
19.3 Iterators
The C# foreach statement is used to iterate over the elements of an enumerable collection. In order to be
enumerable, a collection must have a parameterless GetEnumerator method that returns an enumerator.
Generally, enumerators are difficult to implement, but the task is significantly simplified with iterators.
An iterator is a statement block that yields an ordered sequence of values. An iterator is distinguished from a
normal statement block by the presence of one or more yield statements:
• The yield return statement produces the next value of the iteration.
• The yield break statement indicates that the iteration is complete.
An iterator may be used as the body of a function member as long as the return type of the function member is
one of the enumerator interfaces or one of the enumerable interfaces:
• The enumerator interfaces are System.Collections.IEnumerator and types constructed from
System.Collections.Generic.IEnumerator
• The enumerable interfaces are System.Collections.IEnumerable and types constructed from
System.Collections.Generic.IEnumerable
It is important to understand that an iterator is not a kind of member, but is a means of implementing a function
member. A member implemented via an iterator may be overridden or overloaded by other members which may
or may not be implemented with iterators.
The following Stack
enumerates the elements of the stack in top to bottom order.
using System.Collections.Generic;
public class Stack
{
T[] items;
int count;
public void Push(T data) {...}
public T Pop() {...}
public IEnumerator
for (int i = count – 1; i >= 0; --i) {
yield return items[i];
}
}
}
Chapter 19 Introduction to C# 2.0
Copyright Microsoft Corporation 1999-2003. All Rights Reserved. 9
The presence of the GetEnumerator method makes Stack
Stack
integer stack and then uses a foreach loop to display the values in top to bottom order.
using System;
class Test
{
static void Main() {
Stack
for (int i = 0; i < 10; i++) stack.Push(i);
foreach (int i in stack) Console.Write("{0} ", i);
Console.WriteLine();
}
}
The output of the example is:
9 8 7 6 5 4 3 2 1 0
The foreach statement implicitly calls a collection’s parameterless GetEnumerator method to obtain an
enumerator. There can only be one such parameterless GetEnumerator method defined by a collection, yet it
is often appropriate to have multiple ways of enumerating, and ways of controlling the enumeration through
parameters. In such cases, a collection can use iterators to implement properties or methods that return one of
the enumerable interfaces. For example, Stack
BottomToTop, of type IEnumerable
using System.Collections.Generic;
public class Stack
{
T[] items;
int count;
public void Push(T data) {...}
public T Pop() {...}
public IEnumerator
for (int i = count – 1; i >= 0; --i) {
yield return items[i];
}
}
public IEnumerable
get {
return this;
}
}
public IEnumerable
get {
for (int i = 0; i < count; i++) {
yield return items[i];
}
}
}
}
The get accessor for the TopToBottom property just returns this since the stack itself is an enumerable. The
BottomToTop property returns an enumerable implemented with a C# iterator. The following example shows
how the properties can be used to enumerate stack elements in either order:
using System;
C# 2.0 SPECIFICATION
10 Copyright Microsoft Corporation 1999-2003. All Rights Reserved.
class Test
{
static void Main() {
Stack
for (int i = 0; i < 10; i++) stack.Push(i);
foreach (int i in stack.TopToBottom) Console.Write("{0} ", i);
Console.WriteLine();
foreach (int i in stack.BottomToTop) Console.Write("{0} ", i);
Console.WriteLine();
}
}
Of course, these properties can be used outside of a foreach statement as well. The following example passes
the results of invoking the properties to a separate Print method. The example also shows an iterator used as
the body of a FromToBy method that takes parameters:
using System;
using System.Collections.Generic;
class Test
{
static void Print(IEnumerable
foreach (int i in collection) Console.Write("{0} ", i);
Console.WriteLine();
}
static IEnumerable
for (int i = from; i <= to; i += by) {
yield return i;
}
}
static void Main() {
Stack
for (int i = 0; i < 10; i++) stack.Push(i);
Print(stack.TopToBottom);
Print(stack.BottomToTop);
Print(FromToBy(10, 20, 2));
}
}
The output of the example is:
9 8 7 6 5 4 3 2 1 0
0 1 2 3 4 5 6 7 8 9
10 12 14 16 18 20
The generic and non-generic enumerable interfaces contain a single member, a GetEnumerator method that
takes no arguments and returns an enumerator interface. An enumerable acts as an enumerator factory. Properly
implemented enumerables generate independent enumerators each time their GetEnumerator method is called.
Assuming the internal state of the enumerable has not changed between two calls to GetEnumerator, the two
enumerators returned should produce the same set of values in the same order. This should hold even if the
lifetime of the enumerators overlap as in the following code sample:
using System;
using System.Collections.Generic;
class Test
{
static IEnumerable
while (from <= to) yield return from++;
}
Chapter 19 Introduction to C# 2.0
Copyright Microsoft Corporation 1999-2003. All Rights Reserved. 11
static void Main() {
IEnumerable
foreach (int x in e) {
foreach (int y in e) {
Console.Write("{0,3} ", x * y);
}
Console.WriteLine();
}
}
}
The code above prints a simple multiplication table of the integers 1 through 10. Note that the FromTo method
is invoked only once to generate the enumerable e. However, e.GetEnumerator() is invoked multiple times
(by the foreach statements) to generate multiple equivalent enumerators. These enumerators all encapsulate
the iterator code specified in the declaration of FromTo. Note that the iterator code modifies the from
parameter. Nevertheless, the enumerators act independently because each enumerator is given its own copy of
the from and to parameters. The sharing of transient state between enumerators is one of several common
subtle flaws that should be avoided when implementing enumerables and enumerators. C# iterators are designed
to help avoid these problems and to implement robust enumerables and enumerators in a simple intuitive way.
19.4 Partial types
While it is good programming practice to maintain all source code for a type in a single file, sometimes a type
becomes large enough that this is an impractical constraint. Furthermore, programmers often use source code
generators to produce the initial structure of an application, and then modify the resulting code. Unfortunately,
when source code is emitted again sometime in the future, existing modifications are overwritten.
Partial types allow classes, structs, and interfaces to be broken into multiple pieces stored in different source
files for easier development and maintenance. Additionally, partial types allow separation of machine-generated
and user-written parts of types so that it is easier to augment code generated by a tool.
A new type modifier, partial, is used when defining a type in multiple parts. The following is an example of a
partial class that is implemented in two parts. The two parts may be in different source files, for example
because the first part is machine generated by a database mapping tool and the second part is manually authored:
public partial class Customer
{
private int id;
private string name;
private string address;
private List
public Customer() {
...
}
}
public partial class Customer
{
public void SubmitOrder(Order order) {
orders.Add(order);
}
public bool HasOutstandingOrders() {
return orders.Count > 0;
}
}
When the two parts above are compiled together, the resulting code is the same as if the class had been written
as a single unit:
C# 2.0 SPECIFICATION
12 Copyright Microsoft Corporation 1999-2003. All Rights Reserved.
public class Customer
{
private int id;
private string name;
private string address;
private List
public Customer() {
...
}
public void SubmitOrder(Order order) {
orders.Add(order);
}
public bool HasOutstandingOrders() {
return orders.Count > 0;
}
}
All parts of a partial type must be compiled together such that the parts can be merged at compile-time. Partial
types specifically do not allow already compiled types to be extended.
Chapter 20 Generics
Copyright Microsoft Corporation 1999-2003. All Rights Reserved. 13
20. Generics
20.1 Generic class declarations
A generic class declaration is a declaration of a class that requires type parameters to be supplied in order to
form actual types.
A class declaration may optionally define type parameters:
class-declaration:
attributesopt class-modifiersopt class identifier type-parameter-listopt class-baseopt
type-parameter-constraints-clausesopt class-body ;opt
A class declaration may not supply type-parameter-constraints-clauses (§20.7) unless it also supplies a typeparameter-
list.
A class declaration that supplies a type-parameter-list is a generic class declaration. Additionally, any class
nested inside a generic class declaration or a generic struct declaration is itself a generic class declaration, since
type parameters for the containing type must be supplied to create a constructed type.
Generic class declarations follow the same rules as normal class declarations except where noted, and
particularly with regard to naming, nesting and the permitted access controls. Generic class declarations may be
nested inside non-generic class declarations.
A generic class is referenced using a constructed type (§20.4). Given the generic class declaration
class List
some examples of constructed types are List>. A constructed
type that uses one or more type parameters, such as List
type that uses no type parameters, such as List
Generic types may not be “overloaded”, that is the identifier of a generic type must be uniquely named within a
scope in the same way as ordinary types.
class C {}
class C
class C {} // Error, C defined twice
However, the type lookup rules used during unqualified type name lookup (§20.9.3) and member access
(§20.9.4) do take the number of generic parameters into account.
20.1.1 Type parameters
Type parameters may be supplied on a class declaration. Each type parameter is a simple identifier which
denotes a placeholder for a type argument that is supplied to create a constructed type. A type parameter is a
formal placeholder for a type that will be supplied later. By constrast, a type argument (§20.5.1) is the actual
type that is substituted for the type parameter when a constructed type is referenced.
type-parameter-list:
< type-parameters >
C# 2.0 SPECIFICATION
14 Copyright Microsoft Corporation 1999-2003. All Rights Reserved.
type-parameters:
type-parameter
type-parameters , type-parameter
type-parameter:
attributesopt identifier
Each type parameter in a class declaration defines a name in the declaration space (§3.3) of that class. Thus, it
cannot have the same name as another type parameter or a member declared in that class. A type parameter
cannot have the same name as the type itself.
The scope (§3.7) of a type parameter on a class includes the class-base, type-parameter-constraints-clauses, and
class-body. Unlike members of a class, it does not extend to derived classes. Within its scope, a type parameter
can be used as a type.
type:
value-type
reference-type
type-parameter
Since a type parameter can be instantiated with many different actual type arguments, type parameters have
slightly different operations and restrictions than other types. These include:
• A type parameter cannot be used directly to declare a base class or interface (§20.1.3).
• The rules for member lookup on type parameters depend on the constraints, if any, applied to the type. They
are detailed in §20.7.2.
• The available conversions for a type parameter depend on the constraints, if any, applied to the type. They
are detailed in §20.7.4.
• The literal null cannot be converted to a type given by a type parameter, except if the type parameter is
constrained by a class constraint (§20.7.4). However, a default value expression (§20.8.1) can be used
instead. In addition, a value with a type given by a type parameter can be compared with null using == and
!= (§20.8.4).
• A new expression (§20.8.2) can only be used with a type parameter if the type parameter is constrained by a
constructor-constraint (§20.7).
• A type parameter cannot be used anywhere within an attribute.
• A type parameter cannot be used in a member access or type name to identify a static member or a nested
type (§20.9.1, §20.9.4).
• In unsafe code, a type parameter cannot be used as an unmanaged-type (§18.2).
As a type, type parameters are purely a compile-time construct. At run-time, each type parameter is bound to a
run-time type that was specified by supplying a type argument to the generic type declaration. Thus, the type of
a variable declared with a type parameter will, at run-time, be a closed type (§20.5.2). The run-time execution of
all statements and expressions involving type parameters uses the actual type that was supplied as the type
argument for that parameter.
20.1.2 The instance type
Each class declaration has an associated constructed type, the instance type. For a generic class declaration, the
instance type is formed by creating a constructed type (§20.4) from the type declaration, with each of the
supplied type arguments being the corresponding type parameter. Since the instance type uses the type
parameters, it is only valid where the type parameters are in scope: inside the class declaration. The instance
Chapter 20 Generics
Copyright Microsoft Corporation 1999-2003. All Rights Reserved. 15
type is the type of this for code written inside the class declaration. For non-generic classes, the instance type
is simply the declared class. The following shows several class declarations along with their instance types:
class A
{
class B {} // instance type: A
class C {} // instance type: A
}
class D {} // instance type: D
20.1.3 Base specification
The base class specified in a class declaration may be a constructed class type (§20.4). A base class may not be
a type parameter on its own, though it may involve the type parameters that are in scope.
class Extend
A generic class declaration may not use System.Attribute as a direct or indirect base class.
The base interfaces specified in a class declaration may be constructed interface types (§20.4). A base interface
may not be a type parameter on its own, though it may involve the type parameters that are in scope. The
following code illustrates how a class can implement and extend constructed types:
class C {}
interface I1
class D: C
class E
The base interfaces of a generic class declaration must satisfy the uniqueness rule described in §20.3.1.
Methods in a class that override or implement methods from a base class or interface must provide appropriate
methods of specialized types. The following code illustrates how methods are overridden and implemented.
This is explained further in §20.1.10.
class C
{
public virtual void M1(U x, List
}
interface I1
{
V M2(V);
}
class D: C
{
public override void M1(string x, List
public string M2(string x) {...}
}
20.1.4 Members of generic classes
All members of a generic class may use type parameters from any enclosing class, either directly or as part of a
constructed type. When a particular closed constructed type (§20.5.2) is used at run-time, each use of a type
parameter is replaced with the actual type argument supplied to the constructed type. For example:
class C
{
public V f1;
public C
C# 2.0 SPECIFICATION
16 Copyright Microsoft Corporation 1999-2003. All Rights Reserved.
public C(V x) {
this.f1 = x;
this.f2 = this;
}
}
class Application
{
static void Main() {
C
Console.WriteLine(x1.f1); // Prints 1
C
Console.WriteLine(x2.f1); // Prints 3.1415
}
}
Within instance function members, the type of this is the instance type (§20.1.2) of the declaration.
Apart from the use of type parameters as types, members in generic class declarations follow the same rules as
members of non-generic classes. Additional rules that apply to particular kinds of members are discussed in the
following sections.
20.1.5 Static fields in generic classes
A static variable in a generic class declaration is shared amongst all instances of the same closed constructed
type (§20.5.2), but is not shared amongst instances of different closed constructed types. These rules apply
regardless of whether the type of the static variable involves any type parameters or not.
For example:
class C
{
static int count = 0;
public C() {
count++;
}
public static int Count {
get { return count; }
}
}
class Application
{
static void Main() {
C
Console.WriteLine(C
C
Console.WriteLine(C
C
Console.WriteLine(C
}
}
20.1.6 Static constructors in generic classes
Static constructors in generic classes are used to initialize static fields and perform other initialization for each
different closed constructed type that is created from a particular generic class declaration. The type parameters
of the generic type declaration are in scope and can be used within the body of the static constructor.
Chapter 20 Generics
Copyright Microsoft Corporation 1999-2003. All Rights Reserved. 17
A new closed constructed class type is initialized the first time that either:
• An instance of the closed constructed type is created.
• Any of the static members of the closed constructed type are referenced.
To initialize a new closed constructed class type, first a new set of static fields (§20.1.5) for that particular
closed constructed type is created. Each of the static fields is initialized to its default value (§5.2). Next, the
static field initializers (§10.4.5.1) are executed for those static fields. Finally, the static constructor is executed.
Because the static constructor is executed exactly once for each closed constructed class type, it is a convenient
place to enforce run-time checks on the type parameter that cannot be checked at compile-time via constraints
(§20.6.6). For example, the following type uses a static constructor to enforce that the type parameter is a
reference type:
class Gen
{
static Gen() {
if ((object)T.default != null) {
throw new ArgumentException("T must be a reference type");
}
}
}
20.1.7 Accessing protected members
Within a generic class declaration, access to inherited protected instance members is permitted through an
instance of any class type constructed from the generic class. Specifically, the rules for accessing protected
and protected internal instance members specified in §3.5.3 are augmented with the following rule for
generics:
• Within a generic class G, access to an inherited protected instance member M using a primary-expression of
the form E.M is permitted if the type of E is a class type constructed from G or a class type inherited from a
class type constructed from G.
In the example
class C
{
protected T x;
}
class D
{
static void F() {
D
D
D
dt.x = T.default;
di.x = 123;
ds.x = "test";
}
}
the three assignments to x are permitted because they all take place through instances of class types constructed
from the generic type.
20.1.8 Overloading in generic classes
Methods, constructors, indexers, and operators within a generic class declaration can be overloaded; however,
overloading is constrained so that ambiguities cannot occur within constructed classes. Two function members
declared with the same names in the same generic class declaration must have parameter types such that no
C# 2.0 SPECIFICATION
18 Copyright Microsoft Corporation 1999-2003. All Rights Reserved.
closed constructed type could have two members with the same name and signature. When considering all
possible closed constructed types, this rule includes type arguments that do not currently exist in the current
program, but could be written. Type constraints on the type parameter are ignored for the purpose of this rule.
The following examples show overloads that are valid and invalid according to this rule:
interface I1
interface I2
class G1
{
long F1(U u); // Invalid overload, G
int F1(int i); // members with the same signature
void F2(U u1, U u2); // Valid overload, no type argument for U
void F2(int i, string s); // could be int and string simultaneously
void F3(I1 a); // Valid overload
void F3(I2 a);
void F4(U a); // Valid overload
void F4(U[] a);
}
class G2
{
void F5(U u, V v); // Invalid overload, G2
void F5(V v, U u); // two members with the same signature
void F6(U u, I1
void F6(I1
void F7(U u1, I1
void F7(V v1, U u2); // simultaneously
void F8(ref U u); // Invalid overload
void F8(out V v);
}
class C1 {...}
class C2 {...}
class G3 where U: C1 where V: C2
{
void F9(U u); // Invalid overload, constraints on U and V
void F9(V v); // are ignored when checking overloads
}
20.1.9 Parameter array methods and type parameters
Type parameters may be used in the type of a parameter array. For example, given the declaration
class C
{
static void F(int x, int y, params V[] args);
}
the following invocations of the expanded form of the method:
C
C