条件表达式的短路求值与函数的延迟求值

延迟求值是 .NET的一个很重要的特性,在LISP语言,这个特性是依靠宏来完成的,在C,C++,可以通过函数指针来完成,而在.NET,它是靠委托来完成的。如果不明白什么是延迟求值的同学,我们先看看下面的一段代码:

      static void TestDelayFunction()
        {
            TestDelayFunton1(true,trueFun3);
        }

        static void TestDelayFunton1(bool flag , Func<bool> fun  )
        {
            if(flag)
               fun();
        }

在方法  TestDelayFunton1 中,函数型参数 fun 是否求值,取决于第一个参数  flag,如果它的值为false,那么函数 fun 是永远都不会被求值的,所以,这里函数 fun的求值被推迟到了方法TestDelayFunton1 的内部,而不是在参数计算的时候。

延迟求值很有用,它可以避免我们无谓的计算,比如上面的例子,这样可以节省计算成本,假如 fun的求值很耗时的话。

我们注意这一段代码:

if(flag)

   fun();

其实它等价于一个逻辑表达式:

bool result= flag && fun();

在这个表达式中,fun() 函数是否求值,取决于变量 flag,这个功能叫做“短路”判断,“条件短路”功能正好实现了我们的“延迟求值”的功能,因此,我们可以得到如下推论:

任何时候一个函数fun如果需要延迟求值,那么都可以表示成 一个条件表达式:

(Test() && fun())

所以,前面的2个函数,本质上可以改写成下面的一个函数:

      static void TestDelayFunton2(bool flag)
        {
            bool result = flag && trueFun3();
        }

它将  TestDelayFunton1(true,trueFun3); 的形式调用,转换成了上面的一个函数调用。

当然,要让这种调用变得可用,我们还需要解决一个问题,就是函数 fun()的类型并不是 bool类型,这个问题处理很简单,将函数再包装下即可:

bool WarpFunction()
{
  fun();
  return true;
}

之后的调用将是这个样子的:

(Test() && WarpFunction())

对于本例,它其实等价于:

(flag && trueFun3())

如果是“聪明”的编译器,它是可以完成上面的转换的,下面给出一个完整的代码图片,这样你能够看得更清楚:

上面被标记的部分的2个函数,等价于下面这一个函数,也就是说,TestDelayFunton1 的调用变换成了 TestDelayFunton2的调用。

如果你对上面的这个过程还是不太明白,那么我们看看下面这个例子:

 static bool trueFun1()
        {
            Console.WriteLine("call fun 1");
            return true;
        }

        static bool falseFun2()
        {
            Console.WriteLine("call fun 2");
            return false;
        }

        static bool trueFun3()
        {
            Console.WriteLine("call fun 3");
            return true;
        }

执行下面的代码,trueFun3都会被执行么?

if (trueFun1() && falseFun2() && (trueFun3()))
{ 
            
}
 Console.WriteLine();
if (trueFun1() || falseFun2() || trueFun3())
{

}

假如你非常理解C#的“条件短路”特性,相信答案很快就出来了。

阅读完本文,你可能会问如此奇淫巧技,有何作用?

如果你深入研究.NET的委托,就会明白委托调用其实是将一个函数用对象进行包装,.NET自动为你生成了很多代码,性能上必然有所损耗,假如你在某些地方需要性能极致的代码,那么本文这个技巧一定可以帮助你,假如你还能够写出一个这种转换的编译器来,恭喜你,未来的大神就是你了!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏web编程技术分享

【PHP快速入门】 第二节 php基本语法1.什么地方能写PHP代码?2.PHP语句要不要加分号?3.如果本来该加分号的地方我没加怎么办?4.PHP有注释吗?5.PHP变量怎么去定义的?5.PHP

有的地方要加,有的地方不要加。(似乎是废话...) 一句话搞定:有花括号的地方不要加,其他都给我加上!! 比如,if判断,for循环,定义方法,这些都不要加...

15920
来自专栏Java编程技术

诡异的类型转换

最近在做应用迁移时候遇到了一个诡异的类型转换问题,感觉比较有意思,就记录下来和大家分享下。

11020
来自专栏iOS开发随笔

iOS Swift基础语法(二)

12130
来自专栏java一日一条

Java字符串之性能优化

在程序中你可能时常会需要将别的类型转化成String,有时候可能是一些基础类型的值。在拼接字符串的时候,如果你有两个或者多个基础类型的值需要放到前面,你需要显式...

12720
来自专栏java一日一条

Java字符串之性能优化

在程序中你可能时常会需要将别的类型转化成String,有时候可能是一些基础类型的值。在拼接字符串的时候,如果你有两个或者多个基础类型的值需要放到前面,你需要显式...

10520
来自专栏java一日一条

Java 泛型一览笔录

泛型(Generics )是把类型参数化,运用于类、接口、方法中,可以通过执行泛型类型调用 分配一个类型,将用分配的具体类型替换泛型类型。然后,所分配的类型将用...

10410
来自专栏Golang语言社区

【Go 语言社区】Go语言编程-语法

Go注释 //单行注释 /* */多行注释 Go的内置关键字 default select defer go goto fallthrough rang...

35460
来自专栏芋道源码1024

深入解析Java反射(1) - 基础

> https://www.sczyh30.com/posts/Java/java-reflection-1/ ? 排版有点点崩嘿

10340
来自专栏GreenLeaves

C# foreach循环较for循环的优势与劣势

一、foreach循环的优势 C#支持foreach关键字,foreach在处理集合和数组相对于for存在以下几个优势: 1、foreach语句简洁 2、效率比...

27280
来自专栏java一日一条

基础类型转化成String

在程序中你可能时常会需要将别的类型转化成String,有时候可能是一些基础类型的值。在拼接字符串的时候,如果你有两个或者多个基础类型的值需要放到前面,你需要显式...

9620

扫码关注云+社区

领取腾讯云代金券