C# 3.0 新语言特性 (二)






4.76/5 (22投票s)
一篇关于 C# 3.0 新语言特性的文章
引言
在上一篇文章中,我介绍了一些 C# 3.0 的新语言和编译器特性。在这第二部分中,我将介绍剩余的特性。
- 隐式类型局部变量和数组
- 对象初始化器
- 集合初始化器
- 扩展方法
- 匿名类型
- Lambda 表达式
- 查询关键字
- 自动实现属性
- 部分方法定义
在本文中,我将定义最后五个特性并提供代码示例。
匿名类型
C# 编译器允许你在运行时创建一个在源代码级别不存在的新类型。它将一组只读属性封装到单个对象中,而无需首先显式定义一个类型。属性的类型由编译器推断,编译器可以使用对象初始化器中的属性来创建匿名类型。
例如,考虑以下声明
var person = new { Name = "Mony Hamza", SSN = "12345678" };
在这里,编译器会自动创建一个匿名类型,并从对象初始化器推断出属性的类型。它还会创建与这些属性关联的 private
字段以及必要的 set
和 get
访问器。当实例化对象时,属性将被设置为对象初始化器中指定的值。
以下是声明匿名类型并显示其内容的示例
class MyClass
{
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);
Console.ReadLine();
}
}
输出
Name: Mony Hamza
SSN: 12345678
我收到一个关于为什么使用“隐式类型变量”的问题。如果你要使用匿名变量,那么该变量必须使用 var
关键字进行初始化。我们还需要考虑有关匿名类型的以下注意事项
- 匿名类型是直接从 object 派生的引用类型。从公共语言运行时的角度来看,匿名类型与其他引用类型没有区别。
- 如果两个或多个匿名类型具有相同数量和类型的属性,并且顺序相同,则编译器会将它们视为同一类型,它们共享相同的编译器生成的类型信息。
- 匿名类型具有方法范围。要将匿名类型或包含匿名类型的集合传递到方法边界之外,必须首先将类型强制转换为 object。然而,这会破坏匿名类型的强类型。如果你必须存储查询结果或将其传递到方法边界之外,请考虑使用普通的命名
struct
或cla
而不是匿名类型。ss
- 匿名类型不能包含不安全类型作为属性。
- 由于匿名类型上的
Equals
和GetHashCode
方法是根据属性的Equals
和GetHashcode
定义的,因此同一匿名类型的两个实例只有在所有属性都相等时才相等。
Lambda 表达式
Lambda 表达式为编写包含表达式和语句的匿名方法提供了一种简洁的语法,并且可以用来创建委托。所有 Lambda 表达式都使用 Lambda 运算符 =>
,该运算符读作“goes to”。Lambda 运算符的左侧指定输入参数(如果有),右侧包含表达式或语句块。Lambda 表达式 x => x * x
读作“x
goes to x
times x
。”
在 C# 2.0 中使用匿名方法
public delegate int MyDelegate(int n);
class AnonymouseMethods
{
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));
}
}
在 C# 3.0 中使用 Lambda 表达式
public delegate int MyDelegate(int n);
class LambdaExpresion
{
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();
}
}
输出
The value using an anonymous method is: 50
The value using a lambda expression is: 50
我们也可以在 Lambda 表达式中使用多个参数
public delegate int MyDelegate(int m, int n);
MyDelegate myDelegate = (x, y) => x * y;
Console.WriteLine("The product is: {0}", myDelegate(5, 4));
更多示例
以下示例向你展示如何选择除以二余数为 1 的数字。
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
以下示例说明如何选择以字母 M
开头的 string
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);
}
查询关键字
关键词
from
子句where
子句select
子句group
子句into
orderby
子句join
子句(内连接
、分组连接
、左外连接
)let
子句
要充分理解查询表达式,示例是最佳选择。
以下示例说明如何选择小于 5 且除以二余数为 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
where num < 5 && num % 2 == 0
select num;
// Execute the query.
foreach (int i in lowNums)
{
Console.Write(i + " ");
}
}
以下示例来自 MSDN,它将选择所有分数大于 90 的学生及其分数,为了访问分数内的列表,我们使用了复合 from
子句。
public class Student
{
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
where score > 90
select new { 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();
}
输出
scoreQuery:
Omelchenko Score: 97
O'Donnell Score: 91
Mortensen Score: 94
Garcia Score: 97
Beebe Score: 91
执行连接(此示例也来自 MSDN,展示了如何有条件和无条件地执行交叉连接)。
static void Main()
{
char[] upperCase = { 'A', 'B', 'C'};
char[] lowerCase = { 'x', 'y', 'z'};
var joinQuery1 =
from upper in upperCase
from lower in lowerCase
select new { upper, lower};
var joinQuery2 =
from lower in lowerCase
where lower != 'x'
from upper in upperCase
select new { 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();
}
输出
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
在第一个查询表达式中,它选择 Upper
、Lower
,因此它将每个大写字母与所有小写字母进行匹配。然而,在第二个过滤示例中,它选择 Lower
、Upper
,以便将每个小写字母与所有大写字母进行匹配。
使用 Group By
group
子句返回一个 IGrouping Of (TKey, TElement))
对象的序列,这些对象包含零个或多个与组的键值匹配的项。
以下示例将使其更加清晰
static void Main()
{
string[] Names = { "Mony", "Hamza", "Marica", "John", "Adam", "Olivia" };
var NameGroups =
from name in Names
group name 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);
}
}
}
输出
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
现在让我们对上一示例中的查询做一点修改
var NameGroups =
from name in Names
group name by name[0];
orderby name[0]
结果将是。
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
使用 into
它可以用来创建一个临时标识符,将 group
、join
或 select
子句的结果存储到一个新标识符中。这个标识符本身可以成为其他查询命令的生成器。
//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
group w by w[0] into fruitGroup
where fruitGroup.Count() >= 2
select new { 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();
}
输出
a has 2 elements.
b has 2 elements.
Join
内连接(Inner Join)
namespace Joins
{
public class Department
{
public int ID{get; set;}
public string Name{get; set;}
}
public class Employee
{
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
join dept in Department on employee.DeptID equals dept.ID
select new { EmployeeName = employee.Name,
DeptName = dept.Name };
}
}
上一个示例将返回每个员工姓名和部门名称。
分组连接
带有 into
表达式的 join
子句称为 group join
。
左外部联接
在 left outer join
中,左源序列中的所有元素都会被返回,即使右序列中没有匹配的元素。要执行 left outer join
,请使用 DefaultIfEmpty
方法并结合 group join
。
让我们修改上一个示例来应用 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 =
"No Department"});
现在它会选择所有员工,包括那些尚未分配到部门的员工。
自动实现属性
当不需要额外的逻辑时,你可以使用自动实现的属性,但你必须同时声明 get
和 set
访问器。
//Auto Implemented Properties are used in the previous example, you can check it.
部分方法定义
一个部分 class
或 struct
可以包含一个部分方法。class
的一部分包含方法的签名。可选的实现可以在同一部分或另一部分中定义。如果未提供实现,则在编译时会移除该方法及其所有调用。
希望我成功地使 C# 3.0 的特性变得相当清晰。
期待你的反馈。