文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-04-02 19:55

关注

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

有几点值得注意:


        int x;

        // ERROR: 'x' is not definitely assigned
        Func<int> f = () => x;

注意:可以通过调用分配给 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 表达式内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯