• LINQ之路 3:C# 3.0的语言功能(下)


    在LINQ介绍一篇中,我们已经看到了隐式类型变量var,扩展方法(Extension method)和Lambda表达式的身影。没错,他们正是LINQ技术的基石,是他们让LINQ的实现成为可能,并且简化了LINQ表达式的书写。在这一篇中,我将和大家一一探讨C#3.0在语言功能上所作的努力,包括:扩展方法、Lambda表达式和对象初始化器。 

    扩展方法

    下一个与LINQ密切相关的C# 3.0语言功能是扩展方法(Extension method)。在这之前,一旦一个类型被编译进.NET程序集后,我们便不能再修改该类型的定义了。为该类型添加、修改、删除成员的唯一办法就是修改类型的定义代码。

    但有时候,当需要为类型添加新功能但并不拥有类型的已有代码时,比如,我们想要为.NET库类型List添加自定义的dump方法时,该怎么做呢,答案是扩展方法。扩展方法允许在不修改类型定义的情况下,让该类型获得功能上的扩展。

    定义扩展方法

    当定义一个扩展方法时,第一个限制就是必须把方法定义在静态类中,因此每一个扩展方法也必须声明为静态的。第二个限制是扩展方法要用this关键字对第一个参数进行修饰,这个参数也就是我们希望进行扩展的类型。

    比如下面的扩展方法允许.NET基类库中的所有对象都拥有全新的方法DisplayDefiningAssembly()。

            static class MyExtensions
    {
    // 本方法允许任何对象显示它所处的程序集
    public static void DisplayDefiningAssemlby(this object obj)
    {
    Console.WriteLine("{0} is defined in: {1} ",
    obj.GetType().Name,
    System.Reflection.Assembly.GetAssembly(obj.GetType()));
    }
    }

    调用扩展方法

    我们有两种方式来使用扩展方法,第一种是在实例层次上调用扩展方法,第二种是静态调用扩展方法。

            public void UsingExtensionMethods()
    {
    int myInt = 12345;

    // 1. 在实例层次上调用扩展方法
    myInt.DisplayDefiningAssemlby();

    // 2. 静态调用扩展方法
    MyExtensions.DisplayDefiningAssemlby(myInt);
    }

    实例上,通过一个对象调用它的扩展方法只是编译器的烟幕弹效果而已,背后编译器会转换成静态方法的调用。

    其他注意事项

    上面说到,扩展方法本质上是可以从扩展类型的实例上调用的静态方法。所以它和普通的方法是不一样的,扩展方法不能直接访问扩展类型的成员,从另外一个角度讲,扩展方法即不是直接修改,也不是继承。

    另外一个要注意的地方是:虽然表面上扩展方法是全局的,但其实他们受制于所处的命名空间,要使用在其他命名空间中定义的扩展方法时,我们首先需要导入该命名空间。

    Lambda表达式

    Lambda表达式的引入是与委托类型的使用密切相关的,本质上,Lambda表达式只是用更简单的方式来书写匿名方法,从而彻底简化.NET委托类型的使用。下面我们一步一步的来看看Lambda表达式的简化之路:

    实例找出整数List<T>中的偶数,我们调用了List<T>类型的FindALl()方法,这个方法需要System.Predicate<T>泛型委托,它用于接受类型为T的输入参数并返回一个布尔值。

    传统的委托使用方式

    传统的委托使用方式会为委托目标定义一个单独的方法,如下:

            public static void TraditionalDelegateSyntax()
    {
    List<int> list = new List<int>();
    list.AddRange(new int[] { 1, 5, 10, 20 ,33 });

    //使用传统委托语法调用FindAll
    Predicate<int> callback = new Predicate<int>(IsEvenNumber);
    List<int> evenNumbers = list.FindAll(callback);

    foreach (int num in evenNumbers)
    Console.Write("{0} ", num);
    //Output: 10 20
    }

    // Predicate<>委托的目标
    static bool IsEvenNumber(int i)
    {
    return (i % 2) == 0;
    }

    匿名方法取代显示的委托函数

    这种方式让我们不再需要完整的方法定义,对于一些专门为了委托而定义的函数而言是一个很大的简化,如下:

            public static void AnonymousMethodSyntax()
    {
    List<int> list = new List<int>();
    list.AddRange(new int[] { 1, 5, 10, 20, 33 });

    //使用匿名方法
    List<int> evenNumbers = list.FindAll(
    delegate(int i)
    {
    return (i % 2) == 0;
    });

    foreach (int num in evenNumbers)
    Console.Write("{0} ", num);
    //Output: 10 20
    }

    Lambda表达式

    Lambda表达式让我们进一步简化FindAll()的调用,使用新的语法时,底层的委托语法消失得无影无踪,如下所示:

            public static void LambdaExpressionSyntax()
    {
    List<int> list = new List<int>();
    list.AddRange(new int[] { 1, 5, 10, 20, 33 });

    //使用Lambda表达式
    List<int> evenNumbers = list.FindAll(i => (i % 2) == 0);

    foreach (int num in evenNumbers)
    Console.Write("{0} ", num);
    //Output: 10 20
    }

    Lambda表达式可以应用于任何匿名方法可以应用的场合,而且比匿名方法更加简洁更节省编码时间。其实C#编译器只是把Lambda表达式翻译为相应的普通匿名方法而已。

    Lambda表达式的格式:先定义参数列表,”=>”标记(可读为:goes to)紧随其后,然后是表达式。即:ArgumentsToProcess => StatementsToProcessThem

    Lambda表达式的参数可以是显示类型化的也可以是隐式类型化的。比如上例中的参数i就是隐式类型化的,我们也可以写为如下:

                // 显示定义参数的类型
    List<int> evenNumbers = list.FindAll((int i) => (i % 2) == 0);

    Lambda表达式也可以是一个代码块,其中包含多条代码语句,用花括号括起来即可:

                // 使用语句块编写Lambda表达式
    List<int> evenNumbers = list.FindAll((int i) =>
    {
    Console.WriteLine("processing value: {0}", i);
    bool isEven = (i % 2) == 0;
    return isEven;
    });

    对象初始化器

    C# 3.0提供的 对象初始化器语法用来初始化新类或新结构变量的状态。使用这种语法,我们可以以一种非常简洁的方式来创建对象和为对象的属性赋值。如下:

        public class Point
    {
    public Point() { }
    public Point(int x, int y)
    {
    X = x;
    Y = y;
    }

    public int X { get; set; }
    public int Y { get; set; }
    }

    static void ObjectInitSyntax()
    {
    // 手动初始化各属性
    Point aPoint = new Point();
    aPoint.X = 10;
    aPoint.Y = 20;

    // 使用新的对象初始化语法进行初始化
    Point bPoint = new Point { X = 10, Y = 20 };
    }

    使用初始化语法调用构造函数

    上面的示例中,对象初始化语法会隐式调用默认的构造函数初始化Point实例,而且我们还可以显示调用定制的构造函数,如下:

            static void ObjectInitSyntax()
    {
    // 在这里,默认构造函数被隐式调用
    Point bPoint = new Point { X = 10, Y = 20 };

    // 我们也可以显示调用默认构造函数
    Point cPoint = new Point() { X = 10, Y = 20 };

    // 我们还可以调用自定义的构造函数,只是这里1, 2会被10, 20覆盖
    Point dPoint = new Point(1, 2) { X = 10, Y = 20 };
    }

    初始化内部类型

    当我们用这种语法来初始化一个“复杂”的对象时,其优点会更具说服力,假如我们有类Rectangle如下,可以明显的看出,对象初始化语法不但大大减少了我们敲打键盘的次数,也更加的简洁明了。

        public class Rectangle
    {
    public Point TopLeft { get; set; }
    public Point BottomRight { get; set; }
    }

    static void CompareObjectInitMethods()
    {
    // 传统初始化方法
    Rectangle r = new Rectangle();
    Point p1 = new Point();
    p1.X = 10;
    p1.Y = 10;
    r.TopLeft = p1;
    Point p2 = new Point();
    p2.X = 20;
    p2.Y = 20;
    r.BottomRight = p2;

    // 对象初始化语法
    Rectangle r2 = new Rectangle
    {
    TopLeft = new Point { X = 10, Y = 10 },
    BottomRight = new Point { X = 20, Y = 20 }
    };
    }

    集合的初始化

    集合初始化语法非常类似于对象初始化语法,它使得我们可以像初始化普通数组一样初始化容器(如ArrayList或List<T>)。

            static void CollectionInitSyntax()
    {
    // 初始化标准数组
    int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

    // 初始化一个ArrayList
    ArrayList list = new ArrayList { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

    // 初始化一个List<T>泛型容器
    List<int> list2 = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

    // 如果容器存放的是非简单对象
    List<Point> pointList = new List<Point>
    {
    new Point { X = 2, Y = 2},
    new Point { X = 3, Y = 3}
    };

    // 使用恰当的缩进和嵌套的大括号会使代码易于阅读,同时节省我们的输入时间
    // 想想如果不使用初始化语法构造如下的List,将需要多少行代码
    List<Rectangle> rectList = new List<Rectangle>
    {
    new Rectangle { TopLeft = new Point { X = 1, Y = 1},
    BottomRight = new Point { X = 2, Y = 2}},
    new Rectangle { TopLeft = new Point { X = 3, Y = 3},
    BottomRight = new Point { X = 4, Y = 4}},
    new Rectangle { TopLeft = new Point { X = 5, Y = 5},
    BottomRight = new Point { X = 6, Y = 6}}
    };
    }

     

  • 相关阅读:
    Serverless 时代下大规模微服务应用运维的最佳实践
    Dubbo 跨语言调用神兽:dubbo-go-pixiu
    Flink 1.12 资源管理新特性回顾
    [JDBC] Kettle on MaxCompute 使用指南
    AI运动:阿里体育端智能最佳实践
    MaxCompute非事务表如何更新数据
    如何利用云原生技术构建现代化应用
    实时数仓入门训练营:实时计算 Flink 版 SQL 实践
    实时数仓入门训练营:基于 Apache Flink + Hologres 的实时推荐系统架构解析
    Flink + Iceberg + 对象存储,构建数据湖方案
  • 原文地址:https://www.cnblogs.com/qixuejia/p/4122599.html
Copyright © 2020-2023  润新知