Introduction

C# programming language was first introduced with version 1.0. Visual Studio .NET came equipped with its compiler, and programmers could use the features of the initial version. The second version of C# was released with Visual Studio 2005, adding new capabilities to the previous version. Recently, Microsoft has experimentally released the latest version of the C# language, version 3.0, to programmers worldwide. This version is scheduled to be officially released in 2008 along with Visual Studio 2008. In this article, we will examine the most important features added to C# 3.0.

C# 3.0

Many people learning C# might wonder why the language is evolving so rapidly. It's been less than two years since version 2.0 was released, and now version 3.0 is already here! In response, it should be noted that this progression is completely natural and doesn't create any issues for programmers using C#.

The foundation of the C# language is version 1.0, which defines all the key keywords and core structures of the language. What is added in subsequent versions are only new features that facilitate the coding process.

Before a new version of a language is released, its features and characteristics are made available to the programming community as a specification file. These specifications describe how the new version will function. Those who want to familiarize themselves with the features of C# 3.0 can study the C# 3.0 Specification file. The new features of C# 3.0 described in this file are:

  • Implicitly typed local variables, which permit the type of local variables to be inferred from the expressions used to initialize them.
  • Extension methods, which permit types to be extended with additional static methods without modifying the definition of the type.
  • Lambda expressions, which are a more concise way of writing anonymous function blocks.
  • Object initializers, which permit objects to be initialized without explicitly invoking a constructor.
  • Anonymous types, which permit classes to be defined and instantiated in a single expression.
  • Implicitly typed arrays, which permit the element type of an array to be inferred from the expressions used to initialize the elements.
  • 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).

In the remainder of this article, we will explain the most important features listed above.

Implicitly typed local variables

C# 3.0 introduces the new keyword var that allows programmers to define local variables without explicitly specifying their type. With this feature, defining a string or a numeric value is possible as follows:

var i = 5;
var s = "Hello";
var b = true;

One of the main features of C# is that it is a strongly typed language. This means that when declaring a variable, its type must be explicitly specified by the programmer. Does the addition of this new feature contradict the strongly typed nature of C#?

The answer is NO. Because when we define a variable using the var keyword, the compiler infers the type of the variable from the expression used to initialize it. For example, when we write var i = 5;, the compiler infers that i is of type int. Similarly, when we write var s = "Hello";, the compiler infers that s is of type string. So, the var keyword does not make C# a weakly typed language; it just makes the code more concise.

Let's look at another example:

var x = 10;
var y = "C# 3.0";
var z = false;

In this example, the compiler infers that x is of type int, y is of type string, and z is of type bool. The compiler does this at compile time, not at runtime. So, the var keyword does not affect the performance of the application.

The var keyword is particularly useful when the type of the variable is obvious from the initialization expression, or when the exact type is not important to the programmer, but the fact that the type exists is important.

For example, consider the following code:

var list = new List();
var dict = new Dictionary();

Given the above, the following two declarations are invalid:

var x; // Invalid - no initializer
var y = null; // Invalid - can't infer type from null

Note that var can only be used when defining local variables. It cannot be used in global variable declarations, function parameters, or return values.

Why use var? This feature gives the programmer more freedom when working with local variables. Consider a scenario where a function returns different types of values under different conditions. In this case, without getting involved with casting and type conversion, you can have any type that the function returns by defining an implicit local variable.

Extension methods

C# introduced the sealed keyword to prevent inheritance from a class. By adding this keyword to the beginning of a class definition, it becomes impossible to inherit from it. C# 3.0 provides a new feature that allows programmers to extend any type of class - even classes marked with sealed - using Extension methods. Consider the following example:

// String class members with extension methods

The members of the String class are shown! The String class is one of those classes that cannot be extended through inheritance. But now many functions have been added to it as Extension methods, which are marked with a special icon. The important thing to note is that Extension methods are only added to static classes. Like the example below:

public static class MyConvertor
{
    public static int StringToInt32(this string value)
    {
        return Convert.ToInt32(value);
    }
}

In this example, the static class MyConvertor along with its member function StringToInt32 is defined, which converts string values to numeric values. Notice that MyConvertor adds an extension method to the String class. The syntax for defining an extension method is highlighted with a yellow underline. When defining an extension method, you must use the this keyword conventionally. After the keyword, you must specify the type through which access to the function will be possible. (In the example above, the Str_Int32 function will be accessible through the string class.) After defining the extension method, you can use it in the set of String class functions, which is well illustrated in the figure.

Why Extension methods? As mentioned, many of the classes provided in the .NET Framework are labeled with sealed and cannot be extended. Consider a useful class like List, which is one of these classes. With the introduced feature, programmers will be able to extend these classes by adding their own custom functions as needed.

Lambda expressions

Lambda expressions are a shorter way of writing anonymous functions. In C# 2.0, anonymous functions were introduced, which are functions without a name. These functions are defined in the form of delegates. The following figure shows an example of an anonymous function:

delegate int MathFn(int x, int y);

MathFn add = delegate(int x, int y) { return x + y; };

The equivalent of the above function in the form of a lambda expression is as follows:

MathFn add = (x, y) => x + y;

The parameter list and body of the lambda expression are separated by =>. If the lambda expression definition is more than one line of code, you can show its body using {} as in the following figure:

MathFn add = (x, y) => {
    int result = x + y;
    return result;
};

Below is a practical example of lambda expressions. Consider the MyMath class. A delegate and a function are defined inside this class:

public class MyMath
{
    public delegate int MathFn(int x, int y);
    
    public static int MathInvoker(int x, int y, MathFn operation)
    {
        return operation(x, y);
    }
}

The MathInvoker function is designed to receive one of the four basic operations through the Operation delegate of type MathFn, and using the sent arguments, invoke the corresponding function. Pay attention to the following calls:

// Using anonymous function
int sum1 = MyMath.MathInvoker(5, 3, delegate(int x, int y) { return x + y; });

// Using lambda expression
int sum2 = MyMath.MathInvoker(5, 3, (x, y) => x + y);

The MathInvoker function is first called with an anonymous function parameter and then with a lambda expression. The simplicity of working with lambda expressions compared to anonymous functions is quite evident. (The lambda expression is marked with a blue underline)

// Lambda expressions are more concise than anonymous functions

Why Lambda expressions? Lambda expressions are presented to replace anonymous functions. They have a simpler syntax. Additionally, they are used in Expression trees.

Object initializers

The nature of all modern programs is such that they deal with a vast amount of data. To manage data, we need classes that in software engineering we call Entity Types. These classes are considered as packages of data. The current problem with Entity Types is the multiplicity of their constructors, and you may have encountered this issue as well. In different scenarios, programmers are forced to overload a class constructor in several ways. C# 3.0 provides a fantastic solution to this problem. Object initializer is an advanced form of constructor. Consider an Entity class named Person that packages the following data:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

This class includes three variables, and an identifier is defined for each variable. Now these questions arise: How many ways should the constructor of this class be overloaded? A constructor that initializes all three variables? Perhaps in some cases all three variables are not available, in which case what constructor should be called? C# 3.0 provides the following solution. Suppose we want to create an instance of the Person class:

// Traditional instantiation
Person p1 = new Person();
p1.FirstName = "John";
p1.LastName = "Doe";
p1.Age = 30;

As you can see, in C# 3.0 when instantiating, the programmer has the option to initialize any of the properties in the class as desired and as needed (isn't that great?!) as follows:

// Using object initializer
Person p2 = new Person { FirstName = "Jane", LastName = "Smith", Age = 25 };

// Can initialize only some properties
Person p3 = new Person { FirstName = "Mike", LastName = "Johnson" };

Note that {} is used instead of (). Also, no constructor is defined in the Person class!

Anonymous types

C# 2.0 introduced anonymous functions. C# 3.0 introduces anonymous types. With this feature, programmers will be able to create their desired types inline! Consider the following example:

var anonymousType = new { Name = "John", Age = 30, Email = "[email protected]" };

// Access properties
Console.WriteLine(anonymousType.Name); // John
Console.WriteLine(anonymousType.Age);  // 30

The code provided defines an anonymous type that is available through the local implicit variable named anonymousType. Pay attention to Visual Studio's explanation of the created type. As you can see, all variables mentioned in the advanced constructor are subsequently presented as properties of the anonymous type.

Why Anonymous types? Anonymous types are the best option for producing Entity Types. As mentioned, Entity Types only contain data. Therefore, data received from the user can be packaged in anonymous types in the best way.

Query expressions

The C# design team added an extraordinary feature to it that enables programmers to implement the syntax of query languages like SQL and XQuery using C#! This feature is known by the acronym LINQ and has the following types:

  • LINQ-to-Objects - talks to in-memory objects
  • LINQ-to-SQL - talks to SQL Server databases
  • LINQ-to-XML - talks to hierarchical data represented in XML
  • LINQ-to-DataSets - talks to DataSet objects and underlying DataTables with their relationships
  • LINQ-to-Entities - talks to "entities", part of ADO.NET 3.0

An example of LINQ usage is shown in the figure below:

int[] numbers = { 1, 3, 5, 7, 9, 11, 13 };

var selective_array = from n in numbers
                     where n > 5
                     select n;

Explanation: In the example above, first an int array is defined with initial values. Then using LINQ commands (which are considered C# keywords), an array with members greater than 5 is selected and stored in the local implicit variable selective_array. Finally, the members of selective_array are printed as follows:

// Output: 7, 9, 11, 13
foreach (var item in selective_array)
{
    Console.WriteLine(item);
}

In explaining this new feature, we will suffice with this one example because expressing all aspects of LINQ itself requires writing a detailed article.

The two remaining features

We have fully explained six features of C# 3.0, and these six are of greater importance. Regarding Implicitly typed arrays, it should be said that this feature is about defining array variables using the var keyword. Pay attention to the following examples:

// Implicitly typed arrays
var numbers = new[] { 1, 2, 3, 4, 5 };           // int[]
var names = new[] { "John", "Jane", "Mike" };    // string[]
var mixed = new[] { 1, 2.5, 3.7f };             // double[]

Expression trees are also discussed in relation to lambda expressions. In the explanations related to lambda expressions, it was stated that these expressions are replacements for anonymous functions and therefore have a delegate nature. If we want to give lambda expressions a data nature, the concept of Expression trees is raised. For more complete information, refer to the references published in this field.

Conclusion

In this article, we thoroughly examined C# 3.0. If you've noticed, most of the features presented by the new version facilitate working with data and will save time by reducing the amount of coding required.