新普京网站-澳门新普京 > 前端 > Lambda表达式的前世今生,学习笔记8

Lambda表达式的前世今生,学习笔记8

2019/12/29 22:57

早在 C# 1.0 时,C#中就引入了委托(delegate)类型的概念。通过使用这个类型,我们可以将函数作为参数进行传递。在某种意义上,委托可理解为一种托管的强类型的函数指针。

1.泛型的约束:

通常情况下,使用委托来传递函数需要一定的步骤:

(1)接口约束;

  1. 定义一个委托,包含指定的参数类型和返回值类型。
  2. 在需要接收函数参数的方法中,使用该委托类型定义方法的参数签名。
  3. 为指定的被传递的函数创建一个委托实例。

(2)基类约束,基类约束必须放在第一(假如有多个约束);

可能这听起来有些复杂,不过本质上说确实是这样。上面的第 3 步通常不是必须的,C# 编译器能够完成这个步骤,但步骤 1 和 2 仍然是必须的。

(3)struct/class约束;

幸运的是,在 C# 2.0 中引入了泛型。现在我们能够编写泛型类、泛型方法和最重要的:泛型委托。尽管如此,直到 .NET 3.5,微软才意识到实际上仅通过两种泛型委托就可以满足 99% 的需求:

(4)多个参数类型的约束,每个类型参数都要用where关键字;

  • Action :无输入参数,无返回值
  • Action :支持1-16个输入参数,无返回值
  • Func :支持1-16个输入参数,有返回值

(5)构造器约束,只能是无参构造器,如new();

Action 委托返回 void 类型,Func 委托返回指定类型的值。通过使用这两种委托,在绝大多数情况下,上述的步骤 1 可以省略了。但是步骤 2 仍然是必需的,但仅是需要使用 Action 和 Func。

(6)约束可以由派生类继承,但必须在派生类中显式地指定这些约束;

那么,如果我只是想执行一些代码该怎么办?在 C# 2.0 中提供了一种方式,创建匿名函数。但可惜的是,这种语法并没有流行起来。下面是一个简单的匿名函数的示例:

(7)泛型方法的约束设置与泛型类的约束设置,是一样的;

Func<double, double> square = delegate(double x)
{
return x * x;
};

2.协变性与逆变性:在泛型中,将一个较具体的类型赋给一个较泛化的类型,即是协变。将一个较泛化的类型赋给一个较具体的类型,即是逆变。

为了改进这些语法,在 .NET 3.5 框架和 C# 3.0 中引入了Lambda 表达式。

  协变:在C#4.0中使用out类型参数修饰符允许协变性,该out修饰的类型参数,会导致该类型参数只能用于成员的返回与属性的取值方法,永远不用于输入参数或者属性的赋值方法。

首先我们先了解下 Lambda 表达式名字的由来。实际上这个名字来自微积分数学中的 λ,其涵义是声明为了表达一个函数具体需要什么。更确切的说,它描述了一个数学逻辑系统,通过变量结合和替换来表达计算。所以,基本上我们有 0-n 个输入参数和一个返回值。而在编程语言中,我们也提供了无返回值的 void 支持。

  逆变:在C#4.0中使用in类型参数修饰符允许逆变性,该in修饰的类型参数,会导致该类型参数只能用于成员的输入(输入参数)与属性的设置方法。

让我们来看一些 Lambda 表达式的示例:

  如Contact类继承自PdaItem,若对泛型接口(IConvertible<in T1,out T2>)进行了协变与逆变的设置,就可以成功地从一个IConvertible<PdaItem,Contact>转换成一个IConvertible<Contact,PdaItem>。

// The compiler cannot resolve this, which makes the usage of var impossible! 
  // Therefore we need to specify the type.
  Action dummyLambda = () =>
  {
    Console.WriteLine("Hello World from a Lambda expression!");
  };

  // Can be used as with double y = square(25);
  Func<double, double> square = x => x * x;

  // Can be used as with double z = product(9, 5);
  Func<double, double, double> product = (x, y) => x * y;

  // Can be used as with printProduct(9, 5);
  Action<double, double> printProduct = (x, y) => { Console.WriteLine(x * y); };

  // Can be used as with 
  // var sum = dotProduct(new double[] { 1, 2, 3 }, new double[] { 4, 5, 6 });
  Func<double[], double[], double> dotProduct = (x, y) =>
  {
    var dim = Math.Min(x.Length, y.Length);
    var sum = 0.0;
    for (var i = 0; i != dim; i++)
      sum += x[i] + y[i];
    return sum;
  };

  // Can be used as with var result = matrixVectorProductAsync(...);
  Func<double[,], double[], Task<double[]>> matrixVectorProductAsync =
    async (x, y) =>
    {
      var sum = 0.0;
      /* do some stuff using await ... */
      return sum;
    };

综上所述:协出逆进。

从这些语句中我们可以直接地了解到:

3.数组本身是支持协变与逆变,如PdaItem[] pdaItems=new Contact[]{},Contact[] pdaItems=(Contact)new PdaItem[]{}。

  • 如果仅有一个入参,则可省略圆括号。
  • 如果仅有一行语句,并且在该语句中返回,则可省略大括号,并且也可以省略 return 关键字。
  • 通过使用 async 关键字,可以将 Lambda 表达式声明为异步执行。
  • 大多数情况下,var 声明可能无法使用,仅在一些特殊的情况下可以使用。

4.委托概述:长期以来经验丰富的C、C++程序员利用方法指针,将可执行的步骤(方法)作为参数传递给另一个方法。C#使用委托来提供相同的功能,它将方法作为对象封装起来,允许在运行时间接绑定一个方法的调用,可查看DelegateSample类的代码。

在使用 var 时,如果编译器通过参数类型和返回值类型推断无法得出委托类型,将会抛出 “Cannot assign lambda expression to an implicitly-typed local variable.” 的错误提示。来看下如下这些示例:

5.委托的定义:C#将所有委托的定义成间接派生于System.Delegate,其继承层次为Object -> Delegate ->MulticastDelegate -> 自定义委托。当然我们使用关键delegate声明,其在编译后生成的CIL代码,就是自动继承上面的结构,因此我们不能手动显式继承委托。在委托实例化中,从C@2.0开始可以不使用new来手工创建,直接赋值相同的签名方法即可,编译器会在编译过程中会自动根据委托推断,自动添加new创建,以及把方法名称作为委托参数传递也是一样,

图片 1

6.系统自定义的委托:在.net3.5(C#3.0)中加入了Action与Func这个两个泛型委托,前者是没有返回值的委托,后者是有返回值的委托,在.Net4.0(C#4.0)中又在这些泛型委托中加入了in(逆变)/out(协变)的功能。这些委托的定义省略了我们手工进行自定义委托的情况,因为这些泛型委托都涵盖了我们可能遇到的所有使用情况。(除非你要定义一个有特殊含义的委托名称)。

现在我们已经了解了大部分基础知识,但一些 Lambda 表达式特别酷的部分还没提及。

7.委托使用可变性,即委托类型参数的可变性(协变与逆变),可查看DelegateSample.CovariantAndContravariant()方法的代码。

上一篇:19款绚丽实用的jQuery,CSS3侧边栏菜单【澳门新普京】 下一篇:没有了