C# 3.0 New Language Features (Part 2)
2008-05-02 11:39
519 查看
Introduction
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.
Anonymous Types
The C# compiler enables you to create a new type at runtime which is not available at the source code level. It encapsulates a setof read-only properties into a single object without having to first explicitly define a type. The type of the properties is inferred by the compiler which can create an anonymous type by using the properties in an object initializer.For example, consider the following declaration:
var person = new { Name = "Mony Hamza", SSN = "12345678" };
Here, the compiler automatically creates an anonymous type and infers the types of the properties from the object initializer. It also creates the
privatefields associated with these properties and the necessary
setand
getaccessors. When the object is instantiated, the properties are setto the values specified in the object initializer.
Here is an example for declaring an anonymous type and displaying its content:
classMyClass { static void Main(string[] args) { // Declare an anonymous type: var obj1 = new { Name = "Mony Hamza", SSN ="12345678" }; // Display the contents: Console.WriteLine("Name: {0}\nSSN: {1}", obj1.Name,obj1.SSN); } }
Output:
Name: Mony Hamza SSN: 12345678
I've received a question about why to use "implicitly typed variables". If you are to use an anonymous variable, then this variable must be initialized using the keyword
var. We also have to consider the following notes about Anonymous types:
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
structor
classinstead of an anonymous type.
Anonymous types cannot contain unsafe types as properties.
Because the
Equalsand
GetHashCodemethods on anonymous types are defined in terms of the
Equalsand
GetHashcodeof the properties, two instances of the same anonymous type are equal only if all their properties are equal.
Lambda Expressions
Lambda expressions provide a concise syntaxfor writing anonymous methods that can contain expressions and statements, and can be used to create delegates. All lambda expressions use the lambda operator=>, which is read as "goes to". The left side of the lambda operator specifies the input parameters (if any) and the right side holds the expression or statement block. The lambda expression
x=> x* xis read "
xgoes to
xtimes
x."
Using Anonymous Methods in C# 2.0
public delegate int MyDelegate(int n); classAnonymouseMethods { static void Main() { // Anonymous method that returns the argument multiplied by 10: MyDelegate delegObject1 = new MyDelegate( delegate(int n) { return n * 10; } ); // Display the result: Console.WriteLine("The value is: {0}", delegObject1(5)); } }
Using Lambda Expressions in C# 3.0
public delegate int MyDelegate(int n); classLambdaExpresion { static void Main() { // Anonymous method that returns the argument multiplied by 10: MyDelegate Obj1= new MyDelegate( delegate(int n) { return n * 10; } ); // Display the result: Console.WriteLine("The value using an anonymous method is: {0}", Obj1(5)); // Using lambda expression to do the same job: MyDelegate Obj2 = (int n) => n * 10; // or: // MyDelegate Obj2 = n => n * 10; // Display the result: Console.WriteLine("The value using a lambda expression is: {0}", Obj2(5)); Console.ReadLine(); } }
Output:
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));
More Examples
The following example shows you how to selectthe number which when divided by two has a remainder of 1.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 selectthe
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")); foreach(string i in Filterd) { Console.WriteLine(i); }
Query Keywords
Keywords:fromclause
whereclause
selectclause
groupclause
into
orderbyclause
joinclause (
Inner join,
Group join,
Left outer join)
letclause
To understand Query expressions well, examples are the perfect choice.
The following example illustrates how to selectnumbers less than 5 and when divided by two have a remainder of 0.
static void Main()
{
// A simple data source.
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
// Create the query.
// lowNums is an IEnumerable < int >
var lowNums = from num in numbers
wherenum < 5 && num % 2 == 0
selectnum;
// Execute the query.
foreach (int i in lowNums)
{
Console.Write(i + " ");
}
}
The following example is from MSDN which will selectall the students and their scores greater than 90, in order to access the inner list of scores. We use compound
fromclauses.
Collapse
public classStudent
{
public string LastName { get; set; }
public List < int > Scores {get; set;}
}
static void Main()
{
// 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
from score in student.Scores
wherescore > 90
selectnew { Last = student.LastName, score };
// Execute the queries.
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.");
Console.ReadKey();
}
Output:
scoreQuery: Omelchenko Score: 97 O'Donnell Score: 91 Mortensen Score: 94 Garcia Score: 97 Beebe Score: 91
Perform Joins (this example is also from MSDN which shows how to perform cross joinwith and without conditions).
Collapse
static void Main()
{
char[] upperCase = { 'A', 'B', 'C'};
char[] lowerCase = { 'x', 'y', 'z'};
var joinQuery1 =
from upper in upperCase
from lower in lowerCase
selectnew { upper, lower};
var joinQuery2 =
from lower in lowerCase
wherelower != 'x'
from upper in upperCase
selectnew { lower, upper };
// Execute the queries.
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.");
Console.ReadKey();
}
Output:
Cross join: A is matched to x A is matched to y A is matched to z B is matched to x B is matched to y B is matched to z C is matched to x C is matched to y C is matched to z Filtered non-equijoin: y is matched to A y is matched to B y is matched to C z is matched to A z is matched to B z is matched to C
In the first query expression it selects the
Upper,
Lowerso it matches each upper letter with all the lower ones. However, in the second filter example, it selects
Lower,
Upperso that it matches each lower letter with all the upper ones.
Using Group By
Thegroupclause returns a sequence of
IGrouping Of (TKey, TElement))objects that contain zero or more items that match the key value for the group.
The following example will make it clear:
static void Main()
{
string[] Names = { "Mony", "Hamza", "Marica", "John", "Adam", "Olivia" };
var NameGroups =
from name in Names
groupname by name[0];
// Execute the query.
foreach (var name in NameGroups)
{
Console.WriteLine("Names that start with the letter '{0}':", name.Key);
foreach (var name in NameGroups)
{
Console.WriteLine(name);
}
}
}
Output:
Names that start with the letter 'M': Mony Marica Names that start with the letter 'H': Hamza Names that start with the letter 'J': John Names that start with the letter 'A': Adam Names that start with the letter 'O': Olivia
Now let's add a little modification to the query in the previous example:
var NameGroups =
from name in Names
groupname by name[0];
orderbyname[0]
The result will be:
Names that start with the letter 'A': Adam Names that start with the letter 'H': Hamza Names that start with the letter 'J': John Names that start with the letter 'M': Mony Names that start with the letter 'O': Olivia
Using into
It can be used to create a temporary identifier to store the results of agroup,
joinor
selectclause into a new identifier. This identifier can itself be a generator for additional query commands.
//MSDN example
static void Main()
{
// Create a data source.
string[] words = { "apples", "blueberries", "oranges", "bananas", "apricots"};
// Create the query.
var wordGroups1 =
from w in words
groupw by w[0] into fruitGroup
wherefruitGroup.Count() >= 2
selectnew { FirstLetter = fruitGroup.Key,
Words = fruitGroup.Count() };
// 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.");
Console.ReadKey();
}
Output:
a has 2 elements. b has 2 elements.
Join
Inner JoinCollapse
namespace Joins
{
public classDepartment
{
public int ID{get; set;}
public string Name{get; set;}
}
public classEmployee
{
public int ID{get; set;}
public string Name{get; set;}
public int DeptID{get; set;}
}
public void ManageJoin()
{
List < Department > Departments=new List < Department >
{new Department{ID=1, Name="Sales"},new Department
{ID=2,Name="Marketing"}};
List < Employees > Employees=new List < Employee >
{new Employee {ID=1,Name="Mony",DeptID=1},
new Employee{ID=2,Name="Tom",DeptID=2}};
//Inner Join
var innerJoinQuery = from employee in Employees
joindept in Department on employee.DeptID equals dept.ID
selectnew { EmployeeName = employee.Name,
DeptName = dept.Name };
}
}
The previous example will return each employee name and the department name.
Group Join
A
joinclause with an
intoexpression is called a
groupjoin.
Left Outer Join
In a
left outer join, all the elements in the left source sequence are returned, even if no matching elements are in the right sequence. To perform a
left outer join, use the
DefaultIfEmptymethod in combination with a
groupjoin.
Let's modify the previous example to apply
Left Outer Join:
var LeftouterJoinQuery = from employee in Employees
joindept in Department on employee.DeptID equals dept.ID
selectnew { EmployeeName = employee.Name,
DeptName = dept.Name } into empgroup
selectempgroup.DefaultIfEmpty(new
{ EmployeeName = employee.Name, DeptName =
"No Department"});
Now it selects all the employees including those who are not attached to department yet.
Auto-Implemented Properties
You can use Auto Implemented Properties when no Additional logic is required, but you have to declare both agetand a
setaccessor.
//Auto Implemented Properties are used in the previous example, you can check it.
Partial Method Definitions
A partialclassor
structmay contain a partial method. One part of the
classcontains the signature of the method. An optional implementation may be defined in the same part or another part. If the implementation is not supplied, then the method and all calls to the method are removed at compile time.
Hope that I succeeded in making the C# 3.0 features quite clear.
相关文章推荐
- C# 3.0 New Language Features (Part 1)
- C# 3.0 New Language Features (Part 1)
- C# 3.0 New Language Features (Part 2)
- New Features in C# 3.0
- New Language Features in C# 6
- What's New in the C# 3.0 Language and Compiler
- Some New in C# 3.0 language
- 【翻译】Data Access with LINQ to SQL (1) -- New C# and VB.NET Language Features
- New Features in C# 3.0, 4.0 and 5.0 (英文差的免入)
- C# 7 out variables, tuples & other new features
- C# 3.0新特性初步研究 Part4:使用集合类型初始化器
- Some New Features in C# 6.0
- New features in ARR (Application Request Routing) 3.0
- New DataSet Features in Visual Studio 2005 (C# version)
- Some New Features in C# 6.0
- New features in C# 4.0
- Tiburon - new language features for Delphi 2009(有关泛型的)
- New feature in C# 3.0 - aims to make data programmable in a general purpose and truly object oriented syntax
- C# 3.0新特性初步研究 Part4:使用集合类型初始化器_C#教程
- C# language features that are rarely used but can be useful