Bluffer's Guide to C# 3

Welcome to the bluffer's guide for C# 3.0! This isn't intended to give you enough information to be useful when coding - but you can pretend you know what you're talking about in suitably geeky company. More seriously, it will give you a very rough overview to give some context if you choose to investigate a particular feature further.

Each of the major features of C# 3 is here, with:

  • A brief description of the feature, usually with an example
  • A couple of "extra bluff power" phrases to throw into conversation to get bonus points from your listeners - so long as they don't quiz you further
  • A couple of "call their bluff" claims which sound plausible, but are actually inaccurate. If you feel someone else is perhaps overstating their experience, see if they spot these traps.

Automatic properties

Description

Automatic properties are a simple way to write properties which just get and set their values directly from/to a backing variable. For example, this code:

string name;
public string Name
{
    get { return name; }
    set { name = value; }
}

can be written in C# 3 like this:

public string Name { get; set; }

You can specify separate access levels for the getter and the setter, just like in C# 2.

Extra Bluff Power

  • The compiler-generated backing variable always contains angle brackets (<>) so the variable can never legitimately be referenced in C# code, and it will never clash with a hand-written variable.
  • If you use automatic properties in a struct, you need to include an explicit call to the parameterless constructor from each additional constructor you write.

Call their bluff (untrue statements)

  • Automatic properties are officially called "auto-properties" in the specification. (Reality: They're called automatically implemented properties.)
  • Automatic properties are indistinguishable from normally manually generated properties. (Reality: The compiler adds a CompilerGenerated attribute to both the getter and the setter.)

Object and collection initializers

Description

Object initializers allow you to set the properties of an object very simply at construction time. Collection initializers are similar, allowing you to populate a collection. Examples:

// Object initializer
ProcessStartInfo psi = new ProcessStartInfo 
{ 
    FileName="Notepad.exe", 
    Arguments="c:\\autoexec.bat",
    UseShellExecute=true
};

// Collection initializer for List
List<string> words = new List<string>
{ "the", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog" };

// Collection initializer for Dictionary
Dictionary<string,int> ages = new Dictionary<string,int>
{
    { "Jon", 31 },
    { "Holly", 32 },
    { "Tom", 4 }
};

Extra bluff power

  • You can nest object and collection initializers, creating whole trees of objects.
  • Collection initializers require IEnumerable to be implemented, but the compiler never actually uses anything from the interface.

Call their bluff (untrue statements)

  • Object initializers only work with properties. (Reality: Object initializers can set fields as well, but only if they're accessible.)
  • The Dictionary type has special support particularly for collection initializers. (Reality: the compiler just tries to find an appropriate overload of Add.)

Implicitly typed local variables and arrays

Description

Local variables declared with var are treated as if they were declared with the type of the expression used to initialize the variable. Arrays can be initialized without specifying the type, and the type is inferred from the elements of the initializer. Examples:

// name is of type System.String
var name = "Jon";
// Initializer can use properties, methods etc
var time = DateTime.Now;

// Array type is inferred to be System.String
string[] words = new[] {"hello", "there"};

Extra bluff power

  • You can't use implicit typing (without casting) if the initializer is the null literal
  • The type of an implicitly typed array must be the same as one of the elements of the array - you can't do new[] { "hello", new MemoryStream() } and expect the compiler to an infer an element type of System.Object.

Call their bluff (untrue statements)

  • var is short for "variant". (Reality: the specification doesn't say what var is short for, but it's almost certainly "variable". Variants such as those in COM etc behave very differently, with execution time typing. The variable is still statically typed with var.
  • You can declare the return type of a method to be var. (Reality: this isn't the case in C# 3. It's possible it could become a feature in a future version, using inference over all the types of return statements.)

Anonymous types

Description

Anonymous types persuade the compiler to create types with appropriate constructors and read-only properties, and then create instances of the type. These are primarily used within query expressions, but can be used elsewhere. Example:

var person = new { Name="Jon", Age=31 };

string name = person.Name;
int age = person.Age;

Extra bluff power

  • The generated type (with the MS C# 3 compiler) is actually generic, and may be used for different types which have the same property names and order (but different property types).
  • Equals and GetHashCode are overridden to cater for simple cases of memberwise equality.

Call their bluff (untrue statements)

  • You can't use var with anonymous types. (Reality: a large part of the reason for the existence of var is to support anonymous types. Otherwise you could never declare a variable which used an anonymous type!)
  • Anonymous types are always generated with a name beginning with ??. (Reality: the Microsoft C# 3 compiler always makes anonymous type names start with <>. Starting with ?? would have been just as effective at preventing it from being a valid C# identifier, admittedly, but it's not what happens in the current implementation.)

Lambda expressions

Description

Lambda expressions (expressions with => in them) are similar to anonymous methods in C# 2. They allow closures to be expressed simply, and can be converted into delegates or expression trees (see next point). For example, List contains a ConvertAll method taking a delegate. To convert a List into a List, where each value in the resulting list is half the corresponding value in the original list, we could write:

List<int> original = new List<int> { 0, 1, 2, 3, 4, 5 };
List<double> halved = original.ConvertAll (x => x/2.0);

Extra bluff power

  • There are several "optional" parts to lambda expressions - for example you can optionally put code in braces, and have multiple statements instead of just a single expression.
  • Although lambda expressions are supported in VB9, the statement body form described in the previous point isn't supported.

Call their bluff (untrue statements)

  • Anders Hejlsberg coined the term "lambda expression" in reference to his student fraternity house. (Reality: lambda expressions have been around for a long time, as part of lambda calculus.)
  • Lambda expressions completely replace anonymous methods in C# 3. There are no good reasons ever to use an anonymous method any more. (There's one feature of anonymous methods that isn't shared by lambda expressions. If you don't specify a parameter list at all for an anonymous method, the expression can be converted into an instance of any delegate type which doesn't return a value or have any out parameters. This is handy for creating event handlers which don't care about their parameters at all.)

Expression trees

Description

Expression trees are a way of expressing logic so that other code can interrogate it. When a lambda expression is converted into an expression tree, the compiler doesn't emit the IL for the lambda expression; it IL which will build an expression tree representing the same logic. This is how LINQ providers such as LINQ to SQL work - it converts expression trees into SQL, effectively.

Extra bluff power

  • There are restrictions on what lambda expressions can be converted into expression trees - they have to be single expressions rather than having statement bodies, for instance.
  • You can build up expression trees "manually" using the classes in the System.Linq.Expressions namespace.

Call their bluff (untrue statements)

  • Expression trees can only do very simple things - they can't call methods etc. (Reality: expression trees are very flexible, and can call properties, methods and constructors, use operators, etc.)
  • Expression trees only ever represent delegates. (Reality: expression trees built up from lambda expressions always have a delegate type with the same signature, but you can build up expression trees completely separately, without referring to delegates at all.)

Extension methods

Description

Extension methods are a piece of syntactic sugar to make it look like you're calling an instance method on a value when actually you're calling a static method, passing that value as the first parameter. They're really important when it comes to query expression translation, and are useful in other ways too. Example:

public static class Extensions
{
    public static string Reverse(this string x)
    {
        char[] c = x.ToCharArray();
        Array.Reverse(c);
        return new string(c);
    }
}
...
string greeting = "hello world";
string backwards = greeting.Reverse();
Console.WriteLine(backwards); // Prints "dlrow olleh"

Extra bluff power

  • Extension methods can only be declared in a static, top-level class.
  • Although the compiler uses the System.Runtime.CompilerServices.ExtensionAttribute to find and indiciate extension methods, you can still use them in .NET 2.0 if you define your own ExtensionAttribute in the right namespace.

Call their bluff (untrue statements)

  • Extension methods can only be applied to reference types. (Reality: extension methods can be applied to any type, even primitives such as int. I've defined some handy extension methods to allow expressions such as 19.June(1976) + 8.Hours().)
  • Extension methods can't be generic. (Reality: most of the extensions methods you're likely to come across in the framework - i.e. LINQ - are generic.)

Query expressions

Description

Query expressions are a type of syntax somewhat unlike the rest of C#. It is transformed into non-expression-query C# and then normal compilation is applied. For instance, a query expression of:

var query = from word in words
            where word.Length > 4
            select word.ToUpper();

is translated into:

var query = words.Where(word => word.Length > 4)
                 .Select(word => word.ToUpper());

The above would then typically use extension methods to implement the effective query.

Extra bluff power

  • Query expressions are also known as query comprehensions although the word "comprehension" doesn't appear a single time in the C# 3 specification.
  • It's the magic of lambda expressions being converted into either delegate instances or expression trees that allows query expressions to work against databases, XML, local collections etc, all relatively seamlessly.

Call their bluff (untrue statements)

  • The translation behaviour of query expressions into code without query expressions is compiler-dependent. (Reality: it's very precisely specified, so query expressions should work the same way with different compilers, such as mcs (the Mono C# compiler).
  • Query expressions only work against IEnumerable and derived types such as IQueryable. The translation process has no idea what the rest of the compilation process will do with the translated result. It may result in extension methods being called, it may result in instance methods being called. There may be sequences such as IEnumerable, or a different LINQ provider may work with a completely separate set of types.

You might also like...

Comments

About the author

Jon Skeet United Kingdom

C# MVP currently living in Reading and working for Google.

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“The question of whether computers can think is just like the question of whether submarines can swim.” - Edsger W. Dijkstra