CSharp 3.0 Specification
CSharp 3.0 Specification
Table of Contents
26. Overview of C# 3.0........................................................................................................................................ 1
26.1 Implicitly typed local variables.................................................................................................................. 1
26.2 Extension methods..................................................................................................................................... 2
26.2.1 Declaring extension methods.............................................................................................................. 2
26.2.2 Importing extension methods.............................................................................................................. 3
26.2.3 Extension method invocations............................................................................................................ 3
26.3 Lambda expressions.................................................................................................................................. 4
26.3.1 Lambda expression conversions.......................................................................................................... 6
26.3.2 Type inference.................................................................................................................................... 7
26.3.3 Overload resolution............................................................................................................................. 9
26.4 Object and collection initializers............................................................................................................. 10
26.4.1 Object initializers.............................................................................................................................. 10
26.4.2 Collection initializers........................................................................................................................ 12
26.5 Anonymous types.................................................................................................................................... 13
26.6 Implicitly typed arrays............................................................................................................................. 14
26.7 Query expressions................................................................................................................................... 15
26.7.1 Query expression translation............................................................................................................. 16
26.7.1.1 Select and groupby clauses with continuations........................................................................... 17
26.7.1.2 Explicit iteration variable types.................................................................................................. 17
26.7.1.3 Join clauses................................................................................................................................. 18
26.7.1.4 Let and where clauses................................................................................................................. 20
26.7.1.5 Multiple generators..................................................................................................................... 20
26.7.1.6 Orderby clauses.......................................................................................................................... 21
26.7.1.7 Select clauses.............................................................................................................................. 22
26.7.1.8 Groupby clauses......................................................................................................................... 22
26.7.1.9 Transparent identifiers................................................................................................................ 23
26.7.2 The query expression pattern............................................................................................................ 24
26.8 Expression trees....................................................................................................................................... 25
26.Overview of C# 3.0
C# 3.0 (“C# Orcas”) introduces several language extensions that build on C# 2.0 to support the creation and use
of higher order, functional style class libraries. The extensions enable construction of compositional APIs that
have equal expressive power of query languages in domains such as relational databases and XML. The
extensions include:
• Implicitly typed local variables, which permit the type of local variables to be inferred from the expressions
used to initialize them.
• Extension methods, which make it possible to extend existing types and constructed types with additional
methods.
• Lambda expressions, an evolution of anonymous methods that provides improved type inference and
conversions to both delegate types and expression trees.
• Object initializers, which ease construction and initialization of objects.
• Anonymous types, which are tuple types automatically inferred and created from object initializers.
• Implicitly typed arrays, a form of array creation and initialization that infers the element type of the array
from an array initializer.
• Query expressions, which provide a language integrated syntax for queries that is similar to relational and
hierarchical query languages such as SQL and XQuery.
• Expression trees, which permit lambda expressions to be represented as data (expression trees) instead of as
code (delegates).
This document is a technical overview of those features. The document makes reference to the C# Language
Specification 1.2 (§1 through §18) and the C# Language Specification 2.0 (§19 through §25), both of which are
available on the C# Language Home Page (http://msdn.microsoft.com/vcsharp/language).
The implicitly typed local variable declarations above are precisely equivalent to the following explicitly typed
declarations:
int i = 5;
string s = "Hello";
double d = 1.0;
int[] numbers = new int[] {1, 2, 3};
Dictionary<int,Order> orders = new Dictionary<int,Order>();
A local variable declarator in an implicitly typed local variable declaration is subject to the following
restrictions:
• The declarator must include an initializer.
• The initializer must be an expression. The initializer cannot be an object or collection initializer (§26.4) by
itself, but it can be a new expression that includes an object or collection initializer.
• The compile-time type of the initializer expression cannot be the null type.
• If the local variable declaration includes multiple declarators, the initializers must all have the same
compile-time type.
The following are examples of incorrect implicitly typed local variable declarations:
var x; // Error, no initializer to infer type from
var y = {1, 2, 3}; // Error, collection initializer not permitted
var z = null; // Error, null type not permitted
For reasons of backward compatibility, when a local variable declaration specifies var as the type and a type
named var is in scope, the declaration refers to that type; however, a warning is generated to call attention to
the ambiguity. Since a type named var violates the established convention of starting type names with an upper
case letter, this situation is unlikely to occur.
The for-initializer of a for statement (§8.8.3) and the resource-acquisition of a using statement (§8.13) can be
an implicitly typed local variable declaration. Likewise, the iteration variable of a foreach statement (§8.8.4)
may be declared as an implicitly typed local variable, in which case the type of the iteration variable is inferred
to be the element type of the collection being enumerated. In the example
int[] numbers = { 1, 3, 5, 7, 9 };
foreach (var n in numbers) Console.WriteLine(n);
namespace Acme.Utilities
{
public static class Extensions
{
public static int ToInt32(this string s) {
return Int32.Parse(s);
}
public static T[] Slice<T>(this T[] source, int index, int count) {
if (index < 0 || count < 0 || source.Length – index < count)
throw new ArgumentException();
T[] result = new T[count];
Array.Copy(source, index, result, 0, count);
return result;
}
}
}
Extension methods have all the capabilities of regular static methods. In addition, once imported, extension
methods can be invoked using instance method syntax.
it becomes possible to invoke the extension methods in the static class Extensions using instance method
syntax:
string s = "1234";
int i = s.ToInt32(); // Same as Extensions.ToInt32(s)
int[] digits = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int[] a = digits.Slice(4, 3); // Same as Extensions.Slice(digits, 4, 3)
In the example, B’s method takes precedence over the first extension method, and C’s method takes precedence
over both extension methods.
languages, the anonymous method syntax is rather verbose and imperative in nature. Lambda expressions
provide a more concise, functional syntax for writing anonymous methods.
A lambda expression is written as a parameter list, followed by the => token, followed by an expression or a
statement block.
expression:
assignment
non-assignment-expression
non-assignment-expression:
conditional-expression
lambda-expression
query-expression
lambda-expression:
( lambda-parameter-listopt ) => lambda-expression-body
implicitly-typed-lambda-parameter => lambda-expression-body
lambda-parameter-list:
explicitly-typed-lambda-parameter-list
implicitly-typed-lambda-parameter-list
explicitly-typed-lambda-parameter-list
explicitly-typed-lambda-parameter
explicitly-typed-lambda-parameter-list , explicitly-typed-lambda-parameter
explicitly-typed-lambda-parameter:
parameter-modifieropt type identifier
implicitly-typed-lambda-parameter-list
implicitly-typed-lambda-parameter
implicitly-typed-lambda-parameter-list , implicitly-typed-lambda-parameter
implicitly-typed-lambda-parameter:
identifier
lambda-expression-body:
expression
block
The parameters of a lambda expression can be explicitly or implicitly typed. In an explicitly typed parameter
list, the type of each parameter is explicitly stated. In an implicitly typed parameter list, the types of the
parameters are inferred from the context in which the lambda expression occurs—specifically, when the lambda
expression is converted to a compatible delegate type, that delegate type provides the parameter types (§26.3.1).
In a lambda expression with a single, implicitly typed parameter, the parentheses may be omitted from the
parameter list. In other words, a lambda expression of the form
( param ) => expr
can be abbreviated to
param => expr
Some examples of lambda expressions follow below:
In general, the specification of anonymous methods, provided in §21 of the C# 2.0 Specification, also applies to
lambda expressions. Lambda expressions are a functional superset of anonymous methods, providing the
following additional functionality:
• Lambda expressions permit parameter types to be omitted and inferred whereas anonymous methods require
parameter types to be explicitly stated.
• The body of a lambda expression can be an expression or a statement block whereas the body of an
anonymous method can only be a statement block.
• Lambda expressions passed as arguments participate in type argument inference (§26.3.2) and in method
overload resolution (§26.3.3).
• Lambda expressions with an expression body can be converted to expression trees (§26.8).
• If L has an explicitly typed parameter list, each parameter in D has the same type and modifiers as the
corresponding parameter in L.
• If L has an implicitly typed parameter list, D has no ref or out parameters.
• If D has a void return type and the body of L is an expression, when each parameter of L is given the type
of the corresponding parameter in D, the body of L is a valid expression that would be permitted as a
statement-expression (§8.6).
• If D has a void return type and the body of L is a statement block, when each parameter of L is given the
type of the corresponding parameter in D, the body of L is a valid statement block in which no return
statement specifies an expression.
• If D has a non-void return type and the body of L is an expression, when each parameter of L is given the
type of the corresponding parameter in D, the body of L is a valid expression that is implicitly convertible to
the return type of D.
• If D has a non-void return type and the body of L is a statement block, when each parameter of L is given the
type of the corresponding parameter in D, the body of L is a valid statement block with a non-reachable end
point in which each return statement specifies an expression that is implicitly convertible to the return
type of D.
The examples that follow use a generic delegate type Func<A,R> which represents a function taking an
argument of type A and returning a value of type R:
delegate R Func<A,R>(A arg);
In the assignments
Func<int,int> f1 = x => x + 1; // Ok
Func<int,double> f2 = x => x + 1; // Ok
Func<double,int> f3 = x => x + 1; // Error
the parameter and return types of each lambda expression are determined from the type of the variable to which
the lambda expression is assigned. The first assignment successfully converts the lambda expression to the
delegate type Func<int,int> because, when x is given type int, x + 1 is a valid expression that is implicitly
convertible to type int. Likewise, the second assignment successfully converts the lambda expression to the
delegate type Func<int,double> because the result of x + 1 (of type int) is implicitly convertible to type
double. However, the third assignment is a compile-time error because, when x is given type double, the
result of x + 1 (of type double) is not implicitly convertible to type int.
• If L has an implicitly typed parameter list, when inferred types are substituted for method type parameters in
P and the resulting parameter types are given to the parameters of L, the body of L is a valid expression or
statement block.
• A return type can be inferred for L, as described below.
For each such argument, inferences are made from that argument by relating the return type of P with the
inferred return type of L and the new inferences are added to the accumulated set of inferences. This process is
repeated until no further inferences can be made.
For purposes of type inference and overload resolution, the inferred return type of a lambda expression L is
determined as follows:
• If the body of L is an expression, the type of that expression is the inferred return type of L.
• If the body of L is a statement block, if the set formed by the types of the expressions in the block’s return
statements contains exactly one type to which each type in the set is implicitly convertible, and if that type is
not the null type, then that type is the inferred return type of L.
• Otherwise, a return type cannot be inferred for L.
As an example of type inference involving lambda expressions, consider the Select extension method declared
in the System.Query.Sequence class:
namespace System.Query
{
public static class Sequence
{
public static IEnumerable<S> Select<T,S>(
this IEnumerable<T> source,
Func<T,S> selector)
{
foreach (T element in source) yield return selector(element);
}
}
}
Assuming the System.Query namespace was imported with a using clause, and given a class Customer with
a Name property of type string, the Select method can be used to select the names of a list of customers:
List<Customer> customers = GetCustomerList();
IEnumerable<string> names = customers.Select(c => c.Name);
The extension method invocation (§26.2.3) of Select is processed by rewriting the invocation to a static
method invocation:
IEnumerable<string> names = Sequence.Select(customers, c => c.Name);
Since type arguments were not explicitly specified, type inference is used to infer the type arguments. First, the
customers argument is related to the source parameter, inferring T to be Customer. Then, using the lambda
expression type inference process described above, c is given type Customer, and the expression c.Name is
related to the return type of the selector parameter, inferring S to be string. Thus, the invocation is
equivalent to
Sequence.Select<Customer,string>(customers, (Customer c) => c.Name)
proceeds as follows: First, the argument "1:15:30" is related to the value parameter, inferring X to be
string. Then, the parameter of the first lambda expression, s, is given the inferred type string, and the
The ItemList<T> class has two Sum methods. Each takes a selector argument, which extracts the value to
sum over from a list item. The extracted value can be either an int or a double and the resulting sum is
likewise either an int or a double.
The Sum methods could for example be used to compute sums from a list of detail lines in an order.
class Detail
{
public int UnitCount;
public double UnitPrice;
...
}
void ComputeSums() {
ItemList<Detail> orderDetails = GetOrderDetails(...);
int totalUnits = orderDetails.Sum(d => d.UnitCount);
double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount);
...
}
In the first invocation of orderDetails.Sum, both Sum methods are applicable because the lambda expression
d => d.UnitCount is compatible with both Func<Detail,int> and Func<Detail,double>. However,
overload resolution picks the first Sum method because the conversion to Func<Detail,int> is better than the
conversion to Func<Detail,double>.
In the second invocation of orderDetails.Sum, only the second Sum method is applicable because the
lambda expression d => d.UnitPrice * d.UnitCount produces a value of type double. Thus, overload
resolution picks the second Sum method for that invocation.
added to the collection referenced by the field or property. The field or property must be of a collection type that
satisfies the requirements specified in §26.4.2.
The following class represents a point with two coordinates:
public class Point
{
int x, y;
public int X { get { return x; } set { x = value; } }
public int Y { get { return y; } set { y = value; } }
}
where __p1 and __p2 are temporary variables that are otherwise invisible and inaccessible.
If Rectangle’s constructor allocates the two embedded Point instances
public class Rectangle
{
Point p1 = new Point();
Point p2 = new Point();
public Point P1 { get { return p1; } }
public Point P2 { get { return p2; } }
}
the following construct can be used to initialize the embedded Point instances instead of assigning new
instances:
var r = new Rectangle {
P1 = { X = 0, Y = 1 },
P2 = { X = 2, Y = 3 }
};
The collection object to which a collection initializer is applied must be of a type that implements
System.Collections.Generic.ICollection<T> for exactly one T. Furthermore, an implicit conversion
(§6.1) must exist from the type of each element initializer to T. A compile-time error occurs if these
requirements are not satisfied. A collection initializer invokes the ICollection<T>.Add(T) method for each
specified element in order.
The following class represents a contact with a name and a list of phone numbers:
public class Contact
{
string name;
List<string> phoneNumbers = new List<string>();
public string Name { get { return name; } set { name = value; } }
public List<string> PhoneNumbers { get { return phoneNumbers; } }
}
where __c1 and __c2 are temporary variables that are otherwise invisible and inaccessible.
class __Anonymous1
{
private T1 f1 ;
private T2 f2 ;
…
private Tn fn ;
where each Tx is the type of the corresponding expression ex. It is a compile-time error for an expression in an
anonymous object initializer to be of the null type.
The name of an anonymous type is automatically generated by the compiler and cannot be referenced in
program text.
Within the same program, two anonymous object initializers that specify a sequence of properties of the same
names and types in the same order will produce instances of the same anonymous type. (This definition includes
the order of the properties because it is observable and material in certain circumstances, such as reflection.)
In the example
var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;
the assignment on the last line is permitted because p1 and p2 are of the same anonymous type.
A member declarator can be abbreviated to a simple name (§7.5.2) or a member access (§7.5.4). This is called a
projection initializer and is shorthand for a declaration of and assignment to a property with the same name.
Specifically, member declarators of the forms
identifier expr . identifier
are precisely equivalent to the following, respectively:
identifer = identifier identifier = expr . identifier
Thus, in a projection initializer the identifier selects both the value and the field or property to which the value is
assigned. Intuitively, a projection initializer projects not just a value, but also the name of the value.
not the null type, an array of that type is created. If exactly one type cannot be inferred, or if the inferred type is
the null type, a compile-time error occurs.
The following are examples of implicitly typed array creation expressions:
var a = new[] { 1, 10, 100, 1000 }; // int[]
var b = new[] { 1, 1.5, 2, 2.5 }; // double[]
var c = new[] { "hello", null, "world” }; // string[]
var d = new[] { 1, "one", 2, "two" }; // Error
The last expression causes a compile-time error because neither int nor string is implicitly convertible to the
other. An explicitly typed array creation expression must be used in this case, for example specifying the type to
be object[]. Alternatively, one of the elements can be cast to a common base type, which would then become
the inferred element type.
Implicitly typed array creation expressions can be combined with anonymous object initializers to create
anonymously typed data structures. For example:
var contacts = new[] {
new {
Name = "Chris Smith",
PhoneNumbers = new[] { "206-555-0101", "425-882-8080" }
},
new {
Name = "Bob Harris",
PhoneNumbers = new[] { "650-555-0199" }
}
};
from-let-where-clause:
from-clause
let-clause
where-clause
let-clause:
let identifier = expression
where-clause:
where boolean-expression
orderby-clause:
orderby orderings
orderings:
ordering
orderings , ordering
ordering:
expression ordering-directionopt
ordering-direction:
ascending
descending
select-or-group-clause:
select-clause
group-clause
select-clause:
select expression
group-clause:
group expression by expression
query-continuation:
into identifier join-clausesopt query-body
These methods can be instance methods of the object being queried or extension methods that are external to the
object, and they implement the actual execution of the query.
The translation from query expressions to method invocations is a syntactic mapping that occurs before any type
binding or overload resolution has been performed. The translation is guaranteed to be syntactically correct, but
it is not guaranteed to produce semantically correct C# code. Following translation of query expressions, the
resulting method invocations are processed as regular method invocations, and this may in turn uncover errors,
for example if the methods do not exist, if arguments have wrong types, or if the methods are generic and type
inference fails.
A query expression is processed by repeatedly applying the following translations until no further reductions are
possible. The translations are listed in order of precedence: each section assumes that the translations in the
preceding sections have been performed exhaustively.
Certain translations inject iteration variables with transparent identifiers denoted by *. The special properties of
transparent identifiers are discussed further in §26.7.1.9.
is translated into
from x in ( from … ) …
The translations in the following sections assume that queries have no into continuations.
The example
from c in customers
group c by c.Country into g
select new { Country = g.Key, CustCount = g.Count() }
is translated into
from g in
from c in customers
group c by c.Country
select new { Country = g.Key, CustCount = g.Count() }
is translated into
is translated into
join x in ( e ) . Cast < T > ( ) on k1 equals k2
The translations in the following sections assume that queries have no explicit iteration variable types.
The example
from Customer c in customers
where c.City == "London"
select c
is translated into
from c in customers.Cast<Customer>()
where c.City == "London"
select c
Explicit iteration variable types are useful for querying collections that implement the non-generic
IEnumerable interface, but not the generic IEnumerable<T> interface. In the example above, this would be
the case if customers were of type ArrayList.
is translated into
from t in ( e1 ) . Join ( e2 , x1 => k1 , x2 => k2 , ( x1 , x2 ) => v )
select t
is translated into
from t in ( e1 ) . GroupJoin ( e2 , x1 => k1 , x2 => k2 , ( x1 , g ) => v )
select t
is translated into
from * in (
A from clause followed by a join clause with an into followed by something other than a select clause
from x1 in e1 join x2 in e2 on k1 equals k2 into g
is translated into
from * in (
The translations in the following sections assume that queries have no join clauses.
The example
from c in customers
join o in orders on c.CustomerID equals o.CustomerID
select new { c.Name, o.OrderDate, o.Total }
The example
from c in customers
join o in orders on c.CustomerID equals o.CustomerID into co
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }
is translated into
from * in
from c in customers
join o in orders on c.CustomerID equals o.CustomerID into co
select new { c, co }
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }
where x and y are compiler generated identifiers that are otherwise invisible and inaccessible.
is translated into
from * in (
is translated into
from x in ( e ) . Where ( x => f )
The translations in the following sections assume that queries have no let or where clauses.
The example
from o in orders
let t = o.Details.Sum(d => d.UnitPrice * d.Quantity)
where t >= 1000
select new { o.OrderID, Total = t }
is translated into
from * in
from o in orders
select new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) }
where t >= 1000
select new { o.OrderID, Total = t }
is translated into
from t in ( e1 ) . SelectMany ( x1 => from x2 in e2 select v ) select t
is translated into
from * in (
The translations in the following sections assume that queries have only one from clause.
The example
from c in customers
from o in c.Orders
select new { c.Name, o.OrderID, o.Total }
is translated into
from t in
customers.SelectMany(c =>
from o in c.Orders
select new { c.Name, o.OrderID, o.Total }
)
select t
The example
from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }
is translated into
from * in
from c in customers
from o in c.Orders
select new { c, o }
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }
from x in e orderby k1 , k2 …
is translated into
from x in ( e ) . OrderBy ( x => k1 ) . ThenBy ( x => k2 ) …
The translations in the following sections assume that queries have no orderby clause.
The example
from o in orders
orderby o.Customer.Name, o.Total descending
select o
is translated into
( e ) . Select ( x => v )
For example
from c in customers
select c
is translated into
( e ) . GroupBy ( x => k , x => v )
The example
from c in customers
group c.Name by c.Country
is translated into
customers.
GroupBy(c => c.Country, c => c.Name)
is translated into
from * in
from c in customers
from o in c.Orders
select new { c, o }
orderby o.Total descending
select new { c.Name, o.Total }
from c in customers
join o in orders on c.CustomerID equals o.CustomerID
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }
is translated into
from * in
from * in
from * in
from c in customers
join o in orders o c.CustomerID equals o.CustomerID
select new { c, o }
join d in details on o.OrderID equals d.OrderID
select new { *, d }
join p in products on d.ProductID equals p.ProductID
select new { *, p }
select new { c.Name, o.OrderDate, p.ProductName }
where x, y, and z are compiler generated identifiers that are otherwise invisible and inaccessible.
class C
{
public C<T> Cast<T>();
}
class C<T>
{
public C<T> Where(Func<T,bool> predicate);
public C<U> Select<U>(Func<T,U> selector);
public C<U> SelectMany<U>(Func<T,C<U>> selector);
public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
Func<U,K> innerKeySelector, Func<T,U,V> resultSelector);
public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector);
public O<T> OrderBy<K>(Func<T,K> keySelector);
public O<T> OrderByDescending<K>(Func<T,K> keySelector);
public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector);
public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector,
Func<T,E> elementSelector);
}
class O<T> : C<T>
{
public O<T> ThenBy<K>(Func<T,K> keySelector);
public O<T> ThenByDescending<K>(Func<T,K> keySelector);
}
class G<K,T> : C<T>
{
public K Key { get; }
}
The methods above use the generic delegate types Func<T1, R> and Func<T1, T2, R>, but they could equally
well have used other delegate or expression tree types with the same relationships in parameter and result types.
Notice the recommended relationship between C<T> and O<T> which ensures that the ThenBy and
ThenByDescending methods are available only on the result of an OrderBy or OrderByDescending. Also
notice the recommended shape of the result of GroupBy—a sequence of sequences, where each inner sequence
has an additional Key property.
The Standard Query Operators (described in a separate specification) provide an implementation of the query
operator pattern for any type that implements the System.Collections.Generic.IEnumerable<T>
interface.
The following example represents a lambda expression both as executable code and as an expression tree.
Because a conversion exists to Func<int,int>, a conversion also exists to Expression<Func<int,int>>.
Func<int,int> f = x => x + 1; // Code
Expression<Func<int,int>> e = x => x + 1; // Data
Following these assignments, the delegate f references a method that returns x + 1, and the expression tree e
references a data structure that describes the expression x + 1.
Note
The structure of expression trees will be covered in a separate specification. This specification is not available for the
May 2006 Technology Preview.