C# 並不是唯一一個提供語法糖的程式語言,但卻是少數敢持續在新版本中加進數量堪稱大量語法糖的語言,單以語法糖這點來說,C# 算是發揮得相當極致。
文/黃忠成
所謂語法糖,指的是加進新的語法規則,把原本需要多行的程式碼濃縮進去,就語意上來說,應該與原本的程式碼無異,但事實是如此嗎? 那可不見得,由於編譯器的分叉處理,通常語法糖會走向新的叉路,所以期待它長出濃縮前完全一致的程式碼,是很不現實的天真想法,在某些情況下甚至會更糟,這都是預期內的結果,這也是編譯器需要不停地調整及更新的緣故,這點C# 倒是做的不錯。
我並不是很喜歡使用新事物,原因是不想承受那種不安的感覺,基於個人興趣的關係,對於底層的東西總會多花點時間去了解,當了解越多,越理解到把一件事簡單化必定帶來意料外產物這個道理。這次由於重灌電腦,電腦中不想 安裝那麼多個Visual Studio,所以只留了2017,既然用都用了,也順便把一些語法糖引入舊有程式中,本文以下面這個例子來作主軸。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp3
{
class Program
{
private double _value;
public double Value
{
get
{
return _value;
}
}
static void Main(string[] args)
{
Program p = new Program();
Random r = new Random();
p._value = r.Next(1000);
}
}
}
這個程式中沒有新的語法糖,我的舊程式裡有很多類似的property get/set寫法,由於只用Visual Studio 2017,也就順手調整了其中幾個,變成下面的寫法。
class Program
{
private double _value;
public double Value => _value;
static void Main(string[] args)
{
Program p = new Program();
Random r = new Random();
p._value = r.Next(1000);
}
}
好,那問題來了,請問這兩種屬性存取方式產生的結果是一樣的嗎?這視乎你所謂的結果是什麼,如果是Value的值毫無疑問兩者皆是取用_value,但如果你的結果是過程的話,那可不一定。我會發現過程不同是因為舊程式裡有段作業需要效能,只要是在0.001 ms以上的差距都會影響其效能的表現,該程式在我引入新語法後有了劇烈的變化,致使我追尋其原因,看下面這個測速的例子。
class Program
{
private double _value;
public double Value => _value;
public double NoSugar
{
get
{
return _value;
}
}
static void Main(string[] args)
{
Program p = new Program();
Random r = new Random();
p._value = r.Next(1000);
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 20000000; i++)
{
double s = p.Value;
}
sw.Stop();
Console.WriteLine($"Sugar:{sw.ElapsedTicks}");
sw.Reset();
sw.Start();
for (int i = 0; i < 20000000; i++)
{
double s = p.NoSugar;
}
sw.Stop();
Console.WriteLine($"No Sugar:{sw.ElapsedTicks}");
Console.Read();
}
}
你覺得結果是什麼?
訝異嗎?這代表編譯器產生了不同的程式碼,只是如果使用大多數的反編譯回C#的工具,你多數會得到同樣的程式碼,但只要由IL角度去看,一切就明瞭了。
會影響最後程式碼的除了語法糖外,還有Debug/Release的差異,上面是在Debug模式下的結果,下面是Release。
當然,我們大可以說只要切到Release模式就沒事了(當然,如果你認真的話,會發現就算執行同一種語法,時間也會有些許差距,這是正常的,因為電腦環境及Runtime執行期不同,有些微差距是可以理解的),只是有趣的是,為何新語法糖在Debug/Release會產生同樣的程式碼,而傳統寫法不會?這唯一能解釋的就只有兩種可能,一種是舊的Debug機制需要而產生多餘的程式碼,一種是新的語法糖認為那些多餘的程式碼對於近代的Debug是無用的。
不管如何,不要期待語法糖會產生出與濃縮前一樣的程式碼。
PS: 多數情況下,這種效能差異是可以被忽略的,除非你真的連ms以下都要計較。