首页 科技正文

allbet:C#中的闭包和意想不到的坑

admin 科技 2020-06-20 63 0

虽然闭包主要是函数式编程的玩意儿,而C#的最主要特征是面向工具,然则行使委托或lambda表达式,C#也可以写出具有函数式编程风味的代码。同样的,使用委托或者lambda表达式,也可以在C#中使用闭包。

凭据WIKI的界说,闭包又称语法闭包或函数闭包,是在函数式编程语言中实现语法绑定的一种手艺。闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。闭包也可以延迟变量的生计周期。

嗯。。看界说似乎有点迷糊,让我们看看下面的例子吧

    class Program
    {
        static Action CreateGreeting(string message)
        {
            return () => { Console.WriteLine("Hello " + message); };
        }

        static void Main()
        {
            Action action = CreateGreeting("DeathArthas");
            action();
        }
    }

这个例子异常简朴,用lambda表达式建立一个Action工具,之后再挪用这个Action工具。
然则仔细观察会发现,当Action工具被挪用的时刻,CreateGreeting方式已经返回了,作为它的实参的message应该已经被销毁了,那么为什么我们在挪用Action工具的时刻,照样能够获得准确的效果呢?
 
原来秘密就在于,这里形成了闭包。虽然CreateGreeting已经返回了,然则它的局部变量被返回的lambda表达式所捕捉,延迟了其生命周期。怎么样,这样再回头看闭包界说,是不是更清晰了一些?
 
闭包就是这么简朴,实在我们经常都在使用,只是有时刻我们都不自知而已。好比人人一定都写过类似下面的代码。

void AddControlClickLogger(Control control, string message)
{
	control.Click += delegate
	{
		Console.WriteLine("Control clicked: {0}", message);
	}
}

这里的代码实在就用了闭包,由于我们可以一定,在control被点击的时刻,这个message早就超过了它的声明周期。合理使用闭包,可以确保我们写出在空间和时间上面解耦的委托。
 
不外在使用闭包的时刻,要注重一个陷阱。由于闭包会延迟局部变量的生命周期,在某些情况下程序发生的效果会和预想的不一样。让我们看看下面的例子。

    class Program
    {
	static List<Action> CreateActions()
        {
            var result = new List<Action>();
            for(int i = 0; i < 5; i++)
            {
                result.Add(() => Console.WriteLine(i));
            }
            return result;
        }

        static void Main()
        {
            var actions = CreateActions();
            for(int i = 0;i<actions.Count;i++)
            {
                actions[i]();
            }
        }
    }

这个例子也异常简朴,建立一个Action链表并依次执行它们。看看效果

信赖很多人看到这个效果的脸色是这样的!!岂非不应该是0,1,2,3,4吗?出了什么问题

刨根问底,这儿的问题照样出现在闭包的本质上面,作为“闭包延迟了变量的生命周期”这个硬币的另外一面,是一个变量可能在不经意间被多个闭包所引用。

在这个例子内里,局部变量i同时被5个闭包引用,这5个闭包共享i,以是最后他们打印出来的值是一样的,都是i最后退出循环时刻的值5。

要想解决这个问题也很简朴,多声明一个局部变量,让各个闭包引用自己的局部变量就可以了。

	//其他都保持与之前一致
        static List<Action> CreateActions()
        {
            var result = new List<Action>();
            for (int i = 0; i < 5; i++)
            {
                int temp = i; //添加局部变量
                result.Add(() => Console.WriteLine(temp));
            }
            return result;
        }

这样各个闭包引用差别的局部变量,刚刚的问题就解决了。

除此之外,另有一个修复的方式,在建立闭包的时刻,使用foreach而不是for。至少在C# 7.0 的版本上面,这个问题已经被注重到了,使用foreach的时刻编译器会自动天生代码绕过这个闭包陷阱。

	//这样fix也是可以的
        static List<Action> CreateActions()
        {
            var result = new List<Action>();
            foreach (var i in Enumerable.Range(0,5))
            {
                result.Add(() => Console.WriteLine(i));
            }
            return result;
        }

这就是在闭包在C#中的使用和其使用中的一个小陷阱,希望人人能通过老胡的文章领会到这个知识点并且在开发中少走弯路!

,

联博API接口

www.326681.com采用以太坊区块链高度哈希值作为统计数据,联博以太坊统计数据开源、公平、无任何作弊可能性。联博统计免费提供API接口,支持多语言接入。

版权声明

本文仅代表作者观点,
不代表本站Sunbet的立场。
本文系作者授权发表,未经许可,不得转载。

评论