当前位置: 首页 > 编程笔记 >

C#中函数的创建和闭包的理解

艾骏
2023-03-14
本文向大家介绍C#中函数的创建和闭包的理解,包括了C#中函数的创建和闭包的理解的使用技巧和注意事项,需要的朋友参考一下

动态创建函数

大多数同学,都或多或少的使用过。回顾下c#中动态创建函数的进化:

C# 1.0中:


public delegate string DynamicFunction(string name);

  public static DynamicFunction GetDynamicFunction()

  {

      return GetName;

  }

  static string GetName(string name)

  {

      return name;

  }

  var result = GetDynamicFunction()("mushroom");

3.0写惯了是不是看起来很繁琐、落后。 刚学委托时,都把委托理解成函数指针,也来看下用函数指针实现的:


char GetName(char p);

typedef char (*DynamicFunction)(char p);

DynamicFunction GetDynamicFunction()

{

    return GetName;

}

char GetName(char p)

{

    return p;

};

char result = GetDynamicFunction()('m');

对比起来和c# 1.0几乎一模一样了(引用/指针差别),毕竟是同一家族的。

C# 2.0中,增加匿名函数:


 public delegate string DynamicFunction(string name);

      DynamicFunction result2 = delegate(string name)

      {

          return name;

      };

C# 3.0中,增加Lambda表达式,华丽的转身:


 public static Func<string, string> GetDynamicFunction()

 {

        return name => name;

 }

 var result = GetDynamicFunction()("mushroom");

匿名函数不足之处
虽然增加Lambda表达式,已经极大简化了我们的工作量。但确实有些不足之处:


var result = name => name;


这些写编译时是报错的。因为c#本身强类型语言的,提供var语法糖只是为了省去声明确定类型的工作量。 编译器在编译时必须能够完全推断出各参数的类型才行。代码中的name参数类型,显然在编译时无法推断出来的。

var result = (string name) => name;

Func<string, string> result2 = (string name) => name;

Expression<Func<string, string>> result3 = (string name) => name;

上面直接声明name类型呢,很遗憾这样也是报错的。代码中已经给出答案了,编译器推断不出右边表达式是属于Func<string, string>类型还是Expression<Func<string, string>>类型。


 dynamic result = name => name;

 dynamic result1 = (Func<string,string>)(name => name);


用dynamic呢,同样编译器也分不出右边是个委托,我们显示转换下就可以了。

Func<string, string> function = name => name;

DynamicFunction df = function;


这里定义个func委托,虽然参数和返回值类型都和DynamicFunction委托一样,但编译时还是会报错:不能隐式转换Func<string, string>到DynamicFunction,2个类型是不兼容的。

理解c#中的闭包

谈论到动态创建函数,都要牵扯到闭包。闭包这个概念资料很多了,理论部分这里就不重复了。 来看看c#代码中闭包:


Func<Func<int>> A = () =>

        {

            var age = 18;

            return () =>  //B函数

            {

                return age;

            };

        };

        var result = A()();

上面就是闭包,可理解为就是: 跨作用域访问函数内变量,也有说带着数据的行为。
C#变量作用域一共有三种,即:类变量,实例变量,函数内变量。子作用域访问父作用域的变量(即函数内访问实例/类变量)在我们看来理所当然的,也符合我们一直的编程习惯。
例子中匿名函数B是可以访问上层函数A的变量age。对于编译器而言,A函数是B函数的父作用域,所以B函数访问父作用域的age变量是符合规范的。


int age = 16;

        void Display()

        {

            Console.WriteLine(age);  

            int age = 18;

            Console.WriteLine(age);

        }

上面编译会报错未html" target="_blank">声明使用,编译器检查到函数内声明age后,作用域就会覆盖父作用域的age,(像JS就undefined了)。


        Func<int> C = () =>

         {

             var age = 19;

             return age;

         };


上面声明个同级函数C,那么A函数是无法访C函数中的age变量的。 简单来说就是不可跨作用域访问其他函数内的变量。 那编译器是怎么实现闭包机制的呢?

如上图,答案是升级作用域,把A函数升级为一个实例类作用域。 在编译代码期间,编译器检查到B函数使用A函数内变量时,会自动生成一个匿名类x,把原A函数内变量age提升为x类的字段(即实例变量),A函数提升为匿名类x的实例函数。下面是编译器生成的代码(精简过):


class Program1

{

    static Func<Func<int>> CachedAnonymousMethodDelegate2;

    static void Main(string[] args)

    {

        Func<Func<int>> func = new Func<Func<int>>(Program1.B);

        int num = func()();

    }

    static Func<int> B()

    {

        DisplayClass cl = new DisplayClass();

        cl.age = 18;

        return new Func<int>(cl.A);

    }

}

sealed class DisplayClass

{

    public int age;

    public int A()

    {

        return this.age;

    }

}

我们再来看个复杂点的例子:


static Func<int, int> GetClosureFunction()

    {

        int val = 10;

        Func<int, int> interAdd = x => x + val;

        Console.WriteLine(interAdd(10));

        val = 30;

        Console.WriteLine(interAdd(10));

        return interAdd;

    }

  Console.WriteLine(GetClosureFunction()(30));

输出结果是20、40、60。 当看到这个函数内变量val通过闭包被传递的时候,我们就知道val不仅仅是个函数内变量了。之前我们分析过编译器怎么生成的代码,知道val此时是一个匿名类的实例变量,interAdd是匿名类的实例函数。所以无论val传递多少层,它的值始终保持着,直到离开这个(链式)作用域。

关于闭包,在js当中谈论的比较多,同理,可以对比理解下:


function A() {

    var age = 18;

    return function () {

        return age;

    }

}

A()();

闭包的优点

1.对变量的保护。想暴露一个变量值,但又怕声明类或实例变量会被其他函数污染,这时就可以设计个闭包,只能通过函数调用来使用它。
2.逻辑连续性和变量保持。 A()是执行一部分逻辑,A()()仅接着A()逻辑继续走下去,在这个逻辑上下文期间,变量始终都被保持着,可以随意使用。

 类似资料:
  • 闭包(closure)在 Rust 中也称为 lambda,是一类捕获封闭环境的函数。例如,一个可以捕获 x 变量的闭包如下: |val| val + x 它们的语法和能力使它们在临时(on the fly)使用相当方便。调用一个闭包和调用一个函数完全相同。然而,输入和返回类型两者都可以自动推导,且输入变量名必须指明。 其他的特点包括: 使用 || 替代 () 将输入变量括起来。 区块定界符({}

  • 本文向大家介绍详解Swift中的函数及函数闭包使用,包括了详解Swift中的函数及函数闭包使用的使用技巧和注意事项,需要的朋友参考一下 一、引言 函数是有特定功能的代码段,函数会有一个特定的名称调用时来使用。Swift提供了十分灵活的方式来创建与调用函数。事实上在Swift,每个函数都是一种类型,这种类型由参数和返回值来决定。Swift和Objective-C的一大区别就在于Swift中的函数可以

  • 7.13. 函数文本和闭包 处理函数(handler)中捕捉错误是一些类似的重复代码。如果我们想将捕捉错误的代码封装成一个函数,应该怎么做?GO的函数文本提供了强大的抽象能力,可以帮我们做到这点。 首先,我们重写每个处理函数的定义,让它们接受标题字符串: 定义一个封装函数,接受上面定义的函数类型,返回http.HandlerFunc(可以传送给函数http.HandleFunc)。 func

  • 为什么可以推断闭包表达式的参数类型和返回类型,而不是rust中的函数?

  • 本文向大家介绍深入理解javascript函数参数与闭包,包括了深入理解javascript函数参数与闭包的使用技巧和注意事项,需要的朋友参考一下 最近在学习javascript的函数,函数是javascript的一等对象,想要学好javascript,就必须深刻理解函数。本人把学习的过程整理成文章,一是为了加深自己函数的理解,二是给读者提供学习的途径,避免走弯路。内容有些多,但都是笔者对于函数的

  • 主要内容:Python闭包的__closure__属性前面章节中,已经对 Python 闭包做了初步的讲解,本节将详解介绍到底什么是闭包,以及使用闭包有哪些好处。 闭包,又称闭包函数或者闭合函数,其实和前面讲的嵌套函数类似,不同之处在于,闭包中外部函数返回的不是一个具体的值,而是一个函数。一般情况下,返回的函数会赋值给一个变量,这个变量可以在后面被继续执行调用。 例如,计算一个数的 n 次幂,用闭包可以写成下面的代码: 运行结果为: 4 8 在上面程