C# 3.0 New Language Features (Part 2)
By Mony Hamza
In the previous article, I illustrated some of the C# 3.0 new language and compiler features. I'll illustrate the rest of the features in this second part.
- Implicitly Typed Local Variables and Arrays
- Object Initializers
- Collection Initializers
- Extension Methods
- Anonymous Types
- Lambda Expressions
- Query Keywords
- Auto-Implemented Properties
- Partial Method Definitions
In this article, I will define the last five features and provide code samples.
For example, consider the following declaration:
var person = new { Name = "Mony Hamza", SSN = "12345678" };
Here is an example for declaring an anonymous type and displaying its content:
static void Main(string[] args)
var obj1 = new { Name = "Mony Hamza", SSN ="12345678" };
Console.WriteLine("Name: {0}\nSSN: {1}", obj1.Name,obj1.SSN);
- Anonymous types are reference types that derive directly from object. From the perspective of the common language runtime, an anonymous type is no different from any other reference types.
- If two or more anonymous types have the same number and type of properties in the same order, the compiler treats them as the same type and they share the same compiler-generated type information.
- An anonymous type has method scope. To pass an anonymous type, or a collection that contains anonymous types, outside a method boundary, you must first cast the type to object. However, this defeats the strong typing of the anonymous type. If you must store your query results or pass them outside the method boundary, consider using an ordinary named struct or class instead of an anonymous type.
- Anonymous types cannot contain unsafe types as properties.
- Because the Equals and GetHashCode methods on anonymous types are defined in terms of the Equals and GetHashcode of the properties, two instances of the same anonymous type are equal only if all their properties are equal.
Using Anonymous Methods in C# 2.0
public delegate int MyDelegate(int n);
// Anonymous method that returns the argument multiplied by 10:
MyDelegate delegObject1 = new MyDelegate(
delegate(int n) { return n * 10; }
Console.WriteLine("The value is: {0}", delegObject1(5));
Using Lambda Expressions in C# 3.0
public delegate int MyDelegate(int n);
// Anonymous method that returns the argument multiplied by 10:
MyDelegate Obj1= new MyDelegate(
delegate(int n) { return n * 10; }
Console.WriteLine("The value using an anonymous method is: {0}",
// Using lambda expression to do the same job:
MyDelegate Obj2 = (int n) => n * 10;
// MyDelegate Obj2 = n => n * 10;
Console.WriteLine("The value using a lambda expression is: {0}",
The value using an anonymous method is: 50
The value using a lambda expression is: 50
We also can use more than one parameter in a lambda expression:
public delegate int MyDelegate(int m, int n);
MyDelegate myDelegate = (x, y) => x * y;
Console.WriteLine("The product is: {0}", myDelegate(5, 4));
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
The following examples illustrate how to select the strings which starts with the letter M:
List < string > Names=new List < string >{"Mony","John","Liza"};
List < string > Filterd=Names.FindAll(name =>name.StartsWith("M"));
- from clause
- where clause
- select clause
- group clause
- into
- orderby clause
- join clause (Inner join, Group join, Left outer join)
- let clause
To understand Query expressions well, examples are the perfect choice.
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
// lowNums is an IEnumerable < int >
var lowNums = from num in numbers
public string LastName { get; set; }
public List < int > Scores {get; set;}
// Use a collection initializer to create the data source. Note that
// each element in the list contains an inner sequence of scores.
List < Student> students = new List < Student>
new Student {LastName="Omelchenko", Scores= new List < int> {97, 72, 81, 60}},
new Student {LastName="O'Donnell", Scores= new List < int> {75, 84, 91, 39}},
new Student {LastName="Mortensen", Scores= new List < int> {88, 94, 65, 85}},
new Student {LastName="Garcia", Scores= new List < int> {97, 89, 85, 82}},
new Student {LastName="Beebe", Scores= new List < int> {35, 72, 91, 70}}
// Use a compound from to access the inner sequence within each element.
// Note the similarity to a nested foreach statement.
var scoreQuery = from student in students
select new { Last = student.LastName, score };
Console.WriteLine("scoreQuery:");
foreach (var student in scoreQuery)
Console.WriteLine("{0} Score: {1}", student.Last, student.score);
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
char[] upperCase = { 'A', 'B', 'C'};
char[] lowerCase = { 'x', 'y', 'z'};
Console.WriteLine("Cross join:");
foreach (var pair in joinQuery1)
Console.WriteLine("{0} is matched to {1}", pair.upper, pair.lower);
Console.WriteLine("Filtered non-equijoin:");
foreach (var pair in joinQuery2)
Console.WriteLine("{0} is matched to {1}", pair.lower, pair.upper);
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
The following example will make it clear:
string[] Names = { "Mony", "Hamza", "Marica", "John", "Adam", "Olivia" };
foreach (var name in NameGroups)
Console.WriteLine("Names that start with the letter '{0}':", name.Key);
foreach (var name in NameGroups)
Names that start with the letter 'M':
Names that start with the letter 'H':
Names that start with the letter 'J':
Names that start with the letter 'A':
Names that start with the letter 'O':
Now let's add a little modification to the query in the previous example:
Names that start with the letter 'A':
Names that start with the letter 'H':
Names that start with the letter 'J':
Names that start with the letter 'M':
Names that start with the letter 'O':
string[] words = { "apples", "blueberries", "oranges", "bananas", "apricots"};
group w by w[0] into fruitGroup
select new { FirstLetter = fruitGroup.Key,
// Execute the query. Note that we only iterate over the groups,
// not the items in each group
foreach (var item in wordGroups1)
Console.WriteLine(" {0} has {1} elements.", item.FirstLetter, item.Words);
// Keep the console window open in debug mode
Console.WriteLine("Press any key to exit.");
List < Department > Departments=new List < Department >
{new Department{ID=1, Name="Sales"},new Department
List < Employees > Employees=new List < Employee >
{new Employee {ID=1,Name="Mony",DeptID=1},
new Employee{ID=2,Name="Tom",DeptID=2}};
var innerJoinQuery = from employee in Employees
join dept in Department on employee.DeptID equals dept.ID
select new { EmployeeName = employee.Name,
The previous example will return each employee name and the department name.
A join clause with an into expression is called a group join.
Let's modify the previous example to apply Left Outer Join:
var LeftouterJoinQuery = from employee in Employees
join dept in Department on employee.DeptID equals dept.ID
select new { EmployeeName = employee.Name,
DeptName = dept.Name } into empgroup
select empgroup.DefaultIfEmpty(new
{ EmployeeName = employee.Name, DeptName =
Now it selects all the employees including those who are not attached to department yet.
//Auto Implemented Properties are used in the previous example, you can check it.
Hope that I succeeded in making the C# 3.0 features quite clear.