C# 本地函数与 Lambda 表达式详细介绍

目录
  • 1、C# 本地函数与 Lambda 表达式
  • 2、Lambda 表达式
  • 3、本地函数
  • 4、那么,局部函数的目的是什么?

1、C# 本地函数与 Lambda 表达式

C# 局部函数通常被视为 lambda 表达式的进一步增强。虽然功能是相关的,但也存在重大差异。

Local Functions嵌套函数]功能的 C# 实现。一种语言在支持 lambdas 之后获得对嵌套函数的支持几个版本是有点不寻常的。通常情况相反。

Lambda 或一般的一流函数需要实现未在堆栈上分配且生命周期与需要它们的功能对象相关联的局部变量。如果不依赖垃圾收集或通过捕获列表等解决方案将变量所有权的负担减轻给用户,则几乎不可能正确有效地实现它们。对于某些早期语言来说,这是一个严重的阻塞问题。嵌套函数的简单实现不会遇到这种复杂情况,因此一种语言更常见的是仅支持嵌套函数而不支持 lambda。

无论如何,由于 C# 长期以来一直使用 lambda,因此从差异和相似之处来看本地函数确实是有意义的。

2、Lambda 表达式

Lambda 表达式x => x + x是抽象地表示一段代码以及它如何绑定到其词法环境中的参数和变量的表达式。作为代码的抽象表示,lambda 表达式不能单独使用。为了使用由 lambda 表达式生成的值,需要将其转换为更多内容,例如委托或表达式树。

using System;
using System.Linq.Expressions;

class Program
{
    static void Main(string[] args)
    {
        // can't do much with the lambda expression directly
        // (x => x + x).ToString();  // error

        // can assign to a variable of delegate type and invoke
        Func<int, int> f = (x => x + x);
        System.Console.WriteLine(f(21)); // prints "42"

        // can assign to a variable of expression type and introspect
        Expression<Func<int, int>> e = (x => x + x);
        System.Console.WriteLine(e);     // prints "x => (x + x)"
    }
}

有几点值得注意:

  • lambdas 是产生函数值的表达式。
  • lambda 值的生命周期是无限的——从 lambda 表达式的执行开始,只要存在对该值的任何引用。这意味着 lambda 从封闭方法中使用或“捕获”的任何局部变量都必须在堆上分配。由于 lambda 值的生命周期不受产生它的堆栈帧的生命周期的限制,因此不能在该堆栈帧上分配变量。
  • lambda 表达式要求在执行 lambda 表达式时明确分配主体中使用的所有外部变量。lambda 的第一次和最后一次使用的时刻很少是确定性的,因此该语言假设 lambda 值可以在创建后立即使用,只要它们是可访问的。因此,一个 lambda 值在创建时必须是完全可用的,并且它使用的所有外部变量都必须明确分配。
        int x;

        // ERROR: 'x' is not definitely assigned
        Func<int> f = () => x;
  • lambdas 没有名字,也不能被象征性地引用。特别是 lambda 表达式不能递归声明。

注意:可以通过调用分配给 lambda 的变量或传递给自应用其参数的高阶方法来创建递归 lambda,但这不会表达真正的自我参照。

3、本地函数

局部函数基本上只是在另一个方法中声明的方法,作为一种降低方法对其声明范围内的可见性的方法。

自然地,局部函数中的代码可以访问其包含范围内可访问的所有内容——局部变量、封闭方法的参数、类型参数、局部函数。一个值得注意的例外是外部方法标签的可见性。封闭方法的标签在局部函数中不可见。这只是普通的词法范围,它的工作原理与 lambdas 相同。

public class C
{
    object o;

    public void M1(int p)
    {
        int l = 123;

        // lambda has access to o, p, l,
        Action a = ()=> o = (p + l);
    }

    public void M2(int p)
    {
        int l = 123;

        // Local Function has access to o, p, l,
        void a()
        {
          o = (p + l);
        }
    }
}

与 lambda 的明显区别在于局部函数具有名称并且可以在没有任何间接方式的情况下使用。局部函数可以是递归的。

static int Fac(int arg)
{
    int FacRecursive(int a)
    {
        return a <= 1 ?
                    1 :
                    a * FacRecursive(a - 1);
    }

    return FacRecursive(arg);
}

与 lambda 表达式的主要语义区别在于局部函数不是表达式,它们是声明语句。在代码执行方面,声明是非常被动的实体。事实上,声明并没有真正被“执行”。与标签等其他声明类似,局部函数声明只是将函数引入包含范围,而无需运行任何代码。

更重要的是,无论是声明本身还是嵌套函数的常规调用都不会导致对环境的不确定捕获。在简单和常见的情况下,如普通的调用/返回场景,捕获的局部变量不需要进行堆分配。

例子:

public class C
{
    public void M()
    {
        int num = 123;

        // has access to num
        void  Nested()
        {
           num++;
        }

        Nested();

        System.Console.WriteLine(num);
    }
}

上面的代码大致相当于(反编译):

public class C
{
  // A struct to hold "num" variable.
  // We are not storing it on the heap,
  // so it does not need to be a class
  private struct <>c__DisplayClass0_0
  {
      public int num;
  }

  public void M()
  {
      // reserve storage for "num" in a display struct on the _stack_
      C.<>c__DisplayClass0_0 env = default(C.<>c__DisplayClass0_0);

      // num = 123
      env.num = 123;

      // Nested()
      // note - passes env as an extra parameter
      C.<M>g__a0_0(ref env);

      // System.Console.WriteLine(num)
      Console.WriteLine(env.num);
  }

    // implementation of the the "Nested()".
    // note - takes env as an extra parameter
    // env is passed by reference so it's instance is shared
    // with the caller "M()"
    internal static void <M>g__a0_0(ref C.<>c__DisplayClass0_0 env)
    {
        env.num += 1;
    }
}

请注意,上面的代码直接调用了“Nested()”的实现(不是通过委托间接),并且没有在堆上引入显示存储的分配(就像 lambda 会那样)。局部变量存储在结构中而不是类中。的生命周期num并没有因为它在 中的使用而改变Nested(),所以它仍然可以在栈上分配。M()可以只通过num引用传递,但编译器使用结构体进行打包,因此它可以传递所有本地变量,就像num只使用一个 env 参数一样。

另一个有趣的一点是,只要本地函数在给定范围内可见,就可以使用它们。这是一个重要的事实,使递归和相互递归的场景成为可能。这也使得本地函数声明在源代码中的确切位置在很大程度上变得不重要。

例如,封闭方法的所有变量必须在调用读取它们的本地函数时明确分配,而不是在其声明时。实际上,如果调用可以更早发生,那么在声明时提出该要求将没有任何好处。

public void M()
{
    // error here -
    // Use of unassigned local variable 'num'
    Nested();

    int num;

    // whether 'num' is assigned here or not is irrelevant
    void  Nested()
    {
       num++;
    }

    num = 123;

    // no error here - 'num' is assigned
    Nested();

    System.Console.WriteLine(num);
}

此外 - 如果从未使用过局部函数,它也不会比一段无法访问的代码和任何变量更好,否则它会使用,不需要分配。

public void M()
{
    int num;

    // warning - Nested() is never used.
    void  Nested()
    {
       // no errors on unassigned 'num'.
       // this code never runs.
       num++;
    }
}

4、那么,局部函数的目的是什么?

与 lambdas 相比,局部函数的主要价值主张是局部函数在概念上和运行时开销方面都更简单。

Lambda 可以很好地充当一类函数的角色,但有时您只需要一个简单的助手。分配给局部变量的 Lambda 可以完成这项工作,但存在间接开销、委托分配和可能的闭包开销。私有方法也有效,调用成本更低,但存在封装问题,或缺乏封装。这样的助手对包含类型中的每个人都是可见的。太多这样的帮手会导致严重的混乱。

局部函数非常适合这种情况。调用本地函数的开销与调用私有方法的开销相当,但使用其他不应调用的方法污染包含类型没有问题。

到此这篇关于C# 本地函数与 Lambda 表达式详细介绍的文章就介绍到这了,更多相关C# 本地函数与 Lambda 表达式内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 初步认识C#中的Lambda表达式和匿名方法

    写在前面 元旦三天在家闲着无事,就看了看Linq的相关内容,也准备系统的学习一下,作为学习Linq的前奏,还是先得说说Lambda与匿名方法的知识点.也算是对知识点的查漏补缺吧,也许你会说这没啥大不了的,项目中都在用,但是有些知识,你回头在查看的时候,总会有那么点不一样的收获,这点我是感同身受的,我看书有个习惯,一本书,我能看个三四遍,每次总会有收获.当然,你可以说,当时肯定没认真看,不是那样子的,我认为最直接的原因在于,当时你看是看,没有在真正的项目中遇到过,所以你心里对它的理解并不深,如果在

  • C# Lambda 知识回顾

    序 它是第十一个希腊字母,一个拥有失意.无奈.孤独.低调等含义的流行符号,也指示一款称为"半年命"的游戏. 不过,这次我所讲的是 C# 中的 Lambda. 目录 Lambda 简介 Lambda 表达式 Lambda 语句 异步 Lambda 在 LINQ 中使用 Lambda Lambda 中的类型推断 Lambda 中的变量使用范围 Lambda 的特点 Lambda 简介 Lambda 表达式,是一种简化的匿名函数,可用于创建委托或表达式目录树.其次,你也可以将 Lambda

  • C#3.0中Lambda表达式详解

    在C#2.0中,微软给我们带来了一些新的特性,例如泛型,匿名委托等.然而,这些新的特性多多少少会给人一种从别的语言中"抄"来的感觉(例如泛型类似C++的模板,一些特性类似Java中的一些东西).但是在C#3.0中,微软给我带来的一些新特性可能是以前所有开发语言都没有的特性.这无疑大大的体现了C#3.0在开发语言中强大的优势. Lambda表达式 Lambda 表达式是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托或表达式目录树类型.所有 Lambda 表达式都使用 Lambd

  • C#中方法的直接调用、反射调用与Lambda表达式调用对比

    想调用一个方法很容易,直接代码调用就行,这人人都会.其次呢,还可以使用反射.不过通过反射调用的性能会远远低于直接调用--至少从绝对时间上来看的确是这样.虽然这是个众所周知的现象,我们还是来写个程序来验证一下.比如我们现在新建一个Console应用程序,编写一个最简单的Call方法. 复制代码 代码如下: class Program {     static void Main(string[] args)     {             } public void Call(object o

  • C# lambda表达式应用如何找出元素在list中的索引

    1.先写个规则方法 private bool check(string str){ return str.EndsWith("xxx"); } 2.再写个Predicate Predicate<string> predicate=new Predicate<string>(check)); 如果逻辑不复杂,可以这样写 private void OpenMenu(GameObject gob){ Predicate<string> predicate=

  • C# LINQ查询表达式及对应LAMBDA表达式的用法

    C#编程语言非常优美,我个人还是非常赞同的.特别是在学习一段时间C#后发现确实在它的语法和美观度来说确实要比其它编程语言强一些(也可能是由于VS编译器的加持)用起来非常舒服,而且对于C#我觉得他最优美之处不仅仅是语法糖方面还有就是体现在LINQ和Lambda表达式. 本篇文简单介绍一下关于C#当中LINQ表达式和其对应的Lambda表达式的用法,关于这两部分内容的相关参考资料: 人民邮电出版社<C#程序设计及应用教程>(第3版) 博客:<c# Linq查询> 同时在介绍的时候我会尽

  • C# lambda表达式原理定义及实例详解

    定义:"Lambda表达式"是一个匿名函数,是一种高效的类似于函数式编程的表达式. 好处:Lambda简化了匿名委托的使用,减少开发中需要编写的代码量. 写法:所有Lambda表达式都使用Lambda运算符=>,该运算符读作"goes to".Lambda运算符的左边是输入参数(如果有),右边是表达式或语句块.Lambda表达式x => x * x读作"x goes to x times x". 注:(左边)输入参数为1个时可以省略小

  • 理解C#中的Lambda表达式

    先来看两段代码: 复制代码 代码如下: Thread t = new Thread(() => { AddIt AddDelegate = new AddIt(AddItem); this.Invoke(AddDelegate); }); Thread t3 = new Thread(new ThreadStart(() => { AddIt AddDelegate = new AddIt(AddItem); this.Invoke(AddDelegate); })); 这两种写法都是可以的,

  • C#特性之匿名方法和Lambda表达式

    在我们程序中,经常有这样一些需求: 1.       需要一个临时方法,这个方法只会使用一次,或者使用的很少. 2.       这个方法的方法体很短,以至于比方法声明都短,写起来实在没劲(我将其称之为"一句话方法"). 没办法,这样的方法写起来真是吃力不讨好,比如一些按钮事件处理中,有些按钮点击就是弹出一个对话框,或者调用一下别的什么方法.比如下面的代码: 复制代码 代码如下: this.btnRefresh.Click += new System.EventHandler(this

  • C#匿名委托与Lambda表达式详解

    通过使用匿名委托(匿名方法),使编程变得更加灵活,有关委托与匿名委托请参考我的前一篇Blog<委托与匿名委托>. 继续之前示例,代码如下: static void Main(string[] args) { Worker.TwoNumberHandleMethodDelegate method = delegate(int a, int b) { return a + b; }; Worker worker = new Worker(); int result = worker.HandleT

  • C# Lambda表达式及Lambda表达式树的创建过程

    每次写博客,第一句话都是这样的:程序员很苦逼,除了会写程序,还得会写博客!当然,希望将来的一天,某位老板看到此博客,给你的程序员职工加点薪资吧!因为程序员的世界除了苦逼就是沉默.我眼中的程序员大多都不爱说话,默默承受着编程的巨大压力,除了技术上的交流外,他们不愿意也不擅长和别人交流,更不乐意任何人走进他们的内心! 题外话说多了,咱进入正题: 上一节中,我们讲到:在 2.0 之前的 C# 版本中,声明委托的唯一方法是使用命名方法.C# 2.0 引入了匿名方法,而在 C# 3.0 及更高版本中,La

  • c#基础知识---委托,匿名函数,lambda

    前言: C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针.委托是存有对某个方法的引用的一种引用类型变量.引用可在运行时被改变.委托(Delegate)特别用于实现事件和回调方法.所有的委托都派生自 System.Delegate 类.把一个方法当作参数传递,让其它方法进行调用执行. 1.委托的声明 委托声明决定了可由该委托引用的方法.委托可指向一个与其具有相同标签的方法. 1.1.delegate 1.1.1. 0-23个参数,可以有返回值也可以没有返回值 public d

随机推荐