New features in C# 3.0

September 1st, 2010 Add Your Comments

In this post I will try to present the new things that were introduced in C# 3.0, by creating a short overview which should be able to bring anybody up to speed on them. Readers should be familiar with C# versions 1.0 and 2.0 (you can read our article on C# 2.0 here).

As a small note, usually there is a one to one relationship between the .Net Framework and the C# version. A notable exception was with the .Net Framework 3.0, which was released on C# 2.0. C# 3.0 was released later, together with the .Net Framework 3.5 and Visual Studio 2008.

Linq

This topic is too big to be covered in this document. A good introduction to Linq can be found at the following urls:

Note
Linq adds data manipulation capabilities at language syntax level making code cleaner and easier to read and maintain. Linq itself is not related to accessing database data. Any class implementing IQueryable<T> or IEnumerable<T> will be available via Linq and will be called a Linq provider. You can create your custom providers, but if you need to access common data sources (like XML or JSON files or database data) chances are that such providers have been already built either directly inside the .Net Framework, either by the community as individual projects.

If you want to access database data using Linq you might want to check the ADO .Net Entity Framework, which is shipped with the .Net Framework since version 3.5 SP1, or the open source NHibernate project.

Implicitly typed local variables (var keyword)

Allows for a local variable to be initialized without declaring its type. The variable will take its type implicitly from the value it is assigned to. Here are some properties for implicitly typed variables:

  • You declare an implicitly typed variable using the var keyword. The variable must be initialized in the same statement with declaration. Example:
    // legal declaration
    var strVar = "Hello world";
    
    // illegal declaration
    var strVar;
    strVar = "Hello world";
  • An implicitly typed variable cannot be initialized with the null value and it cannot express a nullable type by adding the question mark after the var keyword.
  • Implicitly typed variables can only be local to methods. They cannot be a class fields, cannot be received as parameters by methods and cannot be returned by methods.
  • Implicitly typed local variables are strong typed. They will take their type from the value they are being assigned and they will keep that type until they run out of scope.
  • The var keyword can be used in foreach constructs. Example:
    string[] messages = new string[] { "Hello", "world", "!" };
    foreach (var s in messages)
        Console.Write(s + " ");
  • var is not actually a C# keyword. You can name variables with the var name and the application will compile. The compiler understands when you want to declare an implicitly typed variable from the context.
  • Do not use the var keyword when you want to declare a variable whose type you know. It is useless and will make the code confusing. Implicitly typed variables are useful only when dealing with anonymous types and linq statements.

Anonymous types

Represent a way to group a set of data inside a single structure without defining a new class for that purpose. Here are some properties of anonymous types:

  • You declare an anonymous type using the object initializer syntax. Example:
    var v1 = new { Name = "Gogu", Age = 25 };
  • For the above example a new class is created by the compiler that will represent the type of the value assigned to v1. You can access the data from v1 by using the generated Name and Age properties. However, all the generated properties for anonymous types are readonly.
  • Every anonymous class inherits directly from System.Object, which means that the compiler will also allow you to declare v1 as object. However, if you do that you will not be able to access its properties anymore, unless you use reflection.
  • The compiler will generate only one class if you declare two anonymous classes that have the same property names, in the same order and that have the same type. Furthermore, the generated class will override the Equals method to check if two instances of the same anonymous type are equal member wise. Example:
    var v1 = new { Name = "Gogu", Age = 25 };
    var v2 = new { Age = 30, Name = "Gelu" };
    var v3 = new { Name = "Parvu", Age = 27 };
    var v4 = new { Nume = "Marin", Varsta = 28 };
    var v5 = new { Name = 26, Age = 26 };
    var v6 = new { Name = "Gogu", Age = 25 };
    
    Console.WriteLine("v1.GetType() == v2.GetType() is {0}", v1.GetType() == v2.GetType());
    Console.WriteLine("v1.GetType() == v3.GetType() is {0}", v1.GetType() == v3.GetType());
    Console.WriteLine("v1.GetType() == v4.GetType() is {0}", v1.GetType() == v4.GetType());
    Console.WriteLine("v1.GetType() == v5.GetType() is {0}", v1.GetType() == v5.GetType());
    
    Console.WriteLine();
    Console.WriteLine("v1 == v6 is {0}", v1 == v6);
    Console.WriteLine("v1.Equals(v6) is {0}", v1.Equals(v6));

    which will output:

    v1.GetType() == v2.GetType() is False
    v1.GetType() == v3.GetType() is True
    v1.GetType() == v4.GetType() is False
    v1.GetType() == v5.GetType() is False
    
    v1 == v6 is False
    v1.Equals(v6) is True
  • As you can see from the above example the == is not overridden in the anonymous type, and it will still check for equality at reference level.
  • The ToString() method for an anonymous type is overridden so that it would print its values. For example this is the ToString() representation of v1 from the above example:
    { Name = Gogu, Age = 25 }
  • The GetHashCode() method of an anonymous class is overridden to compute the hash code in respect to all its fields, so that two instances of the same class will output the same hashcode only if their data is identical.
  • Anonymous class can be nested. Example:
    var pers = new { Name = "Gogu", Age = 25, BirthPlace = new { City = "Munich", County = "Bavaria" } };
    Console.WriteLine(pers.ToString());

    which will output:

    { Name = Gogu, Age = 25, BirthPlace = { City = Munich, County = Bavaria } }
  • You can use anonymous types only inside of the method they have been declared in since there is the single place where you can use the var keyword.
  • Anonymous types cannot contain methods or events, cannot implement interfaces or inherit from any other class than System.Object.
  • Anonymous classes are really useful only when used with Linq.

Object initializer syntax

It used a shorthand method to initialize classes. The following example illustrates the syntax and usage of an object initializer:

private static void Main()
{
    // Person initialized with object initializer syntax.
    // Is equivalent with:
    // Person p = new Person();
    // p.Name = Gogu;
    // p.Age = 25;
    Person p = new Person { Name = "Gogu", Age = 25 };

    Console.WriteLine(p);
}

private class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    public override string ToString()
    {
        return String.Concat("Name: ", this.Name, "; Age: ", this.Age.ToString());
    }
}

The example produces the following output:

Name: Gogu; Age: 25

Any properties or public fields can be initialized in the initializer block. However, you public methods cannot be called inside it.

You are not constrained to use the default constructor for a type when using an initializer block. However, if you use the default constructor, the parentheses following it are optional. For example:

Person p = new Person { Name = "Gogu", Age = 25 };
// is equivalent with
Person p = new Person() { Name = "Gogu", Age = 25 };
// Person initialized with non default constructor
Person p = new Person("Munich") { Name = "Gogu", Age = 25 };

Constructors can be used inside an initialization block. Example:

Rectangle rect = new Rectangle { Location = new Point(10, 10),
Size = new Size(50, 50) };

An useful application of initializer blocks is that they can be used to initialize fields in the initialization stage of a class, that is the stage when fields are initialized in a class just before the constructor is called. Many times, before initializer blocks, fields couldn’t be initialized at initialization stage because no constructor overload had all the arguments that needed to be set to the class instance, so it needed to be set in the constructor. Example:

private class Picture
{
    private Rectangle _picRect = new Rectangle {
        Location = new Point(10, 10), Size = new Size(50, 50) };
}

The initializer block can be considered as part of the constructor syntax. It can be used any place a constructor can be used.

Initializer syntaxes also exist for initializing arrays and collection. For example, an array can now be initialized with the following syntax:

int[] arrInt = {1, 2, 3, 4}; // no 'new int[]' needed anymore

While a collection can be initialized with the following syntax:

List<int> colInt = new List<int> {1, 2, 3, 4};
// 'new List<int>' needed here

Notice that in previous C# versions you couldn’t initialize a collection’s values in the same statement with the constructor call.

Automatic properties

Are a shorthand way to implement implement the properties for fields whose setter simply sets the value and getter simply returns the value. Automatic properties have the syntax of the abstract properties that are used in interfaces. Example:

private class Person
{
    public string Name { get; set; }
}

No private field is needed when using an automatic property. In fact the only way to access the data expressed by the property will be through the property itself.

An automatic property must have both the getter and setter defined. However they can have different accessibility.

An automatic property cannot be initialized at the initialization stage of a class, because there is no syntax for such an operation. It can only be initialized in the constructor.

Lambda expressions

Are no more than just another way to express anonymous methods, with the advantage that they take less space to type and can make the code easier to understand for small functions. You can pass a lambda expression anywhere a delegate is needed. As with anonymous methods, a lambda expression, must match the delegate’s type. Here is an example of a lambda expression in action:

List<int> colInt = new List<int> { 1, 2, 3, 7, 10 };

List<int> filtered = colInt.FindAll(i => i > 5);
foreach (int i in filtered)
    Console.WriteLine(i); // outputs 7 and 10

In the above example the lambda expression is actually i => i > 5 and it is equivalent with:

delegate(int i) { return (i > 5); }

A lambda expression is constructed using the => sign, which is called the lambda operator. At its left you must write the parameters that the expression takes, while at the right you must right the body of the function. In conclusion, the syntax for a lambda expression is the following:

ArgumentsToProcess => StatementsToProcessThem

Note that you don’t need to declare the type of the parameters in a lambda expression. Their type is inferred from the delegate the lambda expression is assigned to, in this case Predicate<int>. If you want, you can explicitly declare the type of the parameters by using the following syntax:

(int i) => i > 5

Note the need for parentheses on the left side of lambda operator. Parentheses are also needed when more than one parameter needs to be passed to the expression or when expression doesn’t receive any parameter. When no parameter is passed to the expression, empty parentheses will be used. Example:

Func<int, int, int> add = ((i, j) =>  i + j);
Console.WriteLine(add(1, 3));

Action sayHello = (() => Console.WriteLine("Hello world!"));
sayHello();

If the expression’s body contains only one statement, no return keyword is needed to return the result of that statement. It will be returned by default.
If the expression’s body needs to have more than just a statement, it must be surrounded by braces. Example:

int i = 0;
Action incAndDisplay = (() => { i++; Console.WriteLine(i); });

for (int j = 0; j < 5; j++)
    incAndDisplay();

Besides the fact that a lambda expression can be used anywhere an anonymous method would be used, lambda expressions are also useful with Linq and expression trees.

Expression trees are a way to represent a lambda function as data. Expression trees provide a way to analyze a method’s code. By doing that the method can be optimized for the runtime context in which it will run. Also, by representing a method as data it can be translated to another expression, like an sql statement or a web service call. Linq uses expression trees to create the sql statements.

You create an expression tree with the same syntax you use to create a lambda expression, except you assign it to an Expression<T> class instance. The compiler will automatically convert it to an expression tree. Example:

Expression<Func<int, int, int>> expr = (x, y) => x+y;

The Expression<T> class can be found in the System.Linq.Expressions namespace. T must be the delegate type of the lambda expression that is going to be converted to the expression tree.

You can call Compile() on an Expression<T> object to obtain the lambda expression that represents it and which you can run in the application.

Only lambda methods with only one statement body can be converted expression trees, which makes them really useful just for linq. More info on expression trees can be found on MSDN or on the following article: http://blogs.msdn.com/charlie/archive/2008/01/31/expression-tree-basics.aspx.

Extension methods

Are used to extend a type’s set of methods. This is useful when the type is defined inside an assembly whose source code you do not have access to. Without extension methods, methods can be added to a type by generating them with the System.Reflection.Emit namespace.

Extension methods can only be defined inside static classes and of course only as static methods. The first parameter of a static method must contain the this keyword, followed by the name of the type it extends, followed by the name of the parameter. The actual parameters of the extension method will start from the second parameter. Example:

static class StringExtender
{
    public static void Display(this string str)
    {
        Console.WriteLine(str);
    }
}

class Program
{
    static void Main(string[] args)
    {
        string str = "Hello world!";
        str.Display();
    }
}

which outputs:

Hello world!

Classes, structures and interfaces can be extended using extension methods. However, when extending interfaces, extension methods must provide the method’s body, which makes sense, since all the classes that have implemented the interface didn’t know at that time of the extension methods and didn’t implement them.

Although defined in classes, extension methods are scoped at namespace level. In order to have access to extension methods, the namespace of the extension class must be included in the calling file.

Extension methods can also be statically called by using the extension class in which they are defined.

An extension method can only access the public members of the class it extends.

If an extension method is created for a method that is already available in a type, the extension method is useless since it will never be called. There is no way for an extension method to override an actual method of a class.

Extension methods, like most of the features from C# 3.0 were created to support Linq. While extension methods might prove to be useful, they should be used with extreme care since they tend to make the code confusing. Furthermore, it is good practice to declare all the extension methods in their own assembly and have their own namespaces. Also, consider the fact that extension methods shouldn’t be used when inheritance is an option.

Partial methods

Are used to declare the same method in multiple source files. While this is not as useful as partial classes, it might also be used with code generators. Partial methods follow the following rules:

  • Partial methods can consist of only two parts: declaration and implementation. The declaration cannot have a body. Definition and declaration can be found in the same of a partial class.
  • The implementation of a partial method can be omitted. If that is the case the compiler will remove the method from the class and also all the calls made to it. No exception will be thrown at runtime.
  • Partial method declarations must begin with the partial keyword and the method must return void.
  • Partial methods can have ref but not out parameters.
  • Partial methods are implicitly private, and therefore they cannot be virtual.
  • Partial methods cannot be extern, because the presence of the body determines whether they are defining or implementing.
  • Partial methods can have static and unsafe modifiers.
  • Partial methods can be generic. Constraints are put on the defining partial method declaration, and may optionally be repeated on the implementing one. Parameter and type parameter names do not have to be the same in the implementing declaration as in the defining one.
  • You cannot make a delegate to a partial method.

Example:

partial void MyInit();

partial void MyInit()
{
	Console.WriteLine("Initializing ...");
}

Comments are closed.