65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 3.0 新语言特性 (二)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (22投票s)

2008年1月24日

CPOL

5分钟阅读

viewsIcon

69631

一篇关于 C# 3.0 新语言特性的文章

引言

上一篇文章中,我介绍了一些 C# 3.0 的新语言和编译器特性。在这第二部分中,我将介绍剩余的特性。

  1. 隐式类型局部变量和数组
  2. 对象初始化器
  3. 集合初始化器
  4. 扩展方法
  5. 匿名类型
  6. Lambda 表达式
  7. 查询关键字
  8. 自动实现属性
  9. 部分方法定义

在本文中,我将定义最后五个特性并提供代码示例。

匿名类型

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 class 而不是匿名类型。
  • 匿名类型不能包含不安全类型作为属性。
  • 由于匿名类型上的 EqualsGetHashCode 方法是根据属性的 EqualsGetHashcode 定义的,因此同一匿名类型的两个实例只有在所有属性都相等时才相等。

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);
}

查询关键字

关键词

  1. from 子句
  2. where 子句
  3. select 子句
  4. group 子句
  5. into
  6. orderby 子句
  7. join 子句(内连接分组连接左外连接
  8. 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

在第一个查询表达式中,它选择 UpperLower,因此它将每个大写字母与所有小写字母进行匹配。然而,在第二个过滤示例中,它选择 LowerUpper,以便将每个小写字母与所有大写字母进行匹配。

使用 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

它可以用来创建一个临时标识符,将 groupjoinselect 子句的结果存储到一个新标识符中。这个标识符本身可以成为其他查询命令的生成器。

//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 的特性变得相当清晰。

期待你的反馈。

© . All rights reserved.