條件表示式的短路求值與函式的延遲求值
延遲求值是 .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自動為你生成了很多程式碼,效能上必然有所損耗,假如你在某些地方需要效能極致的程式碼,那麼本文這個技巧一定可以幫助你,假如你還能夠寫出一個這種轉換的編譯器來,恭喜你,未來的大神就是你了!