依據前篇文章:參數傳入 dynamic 會讓函式傳回值也變成 dynamic,導致無法使用 LINQ Lambda 運算式。文末提到,依據方法多載(Method Overloading)與 dynamic一文的研究心得,.NET 呼叫函式時若遇到參數為 dynamic 時,將改用System.Runtime.CompilerServices、System.CSharp.RuntimeBinder 命名空間物件與方法間接觸發,程序曲拆繁瑣許多。由此推測,參數傳入 dynamic 型別肯定會產生效能損耗,好奇心驅使之下,索性寫幾行程式實測親見為憑。
我設計測試程式如下,執行 100 萬次 "2017/08/26".Split('/').Length,連跑 10 回合測量執行時間,Test1() 與 Test2() 只差在 "2017/08/26" 宣告為 string 還是 dynamic:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestDynamic
{
class Program
{
staticvoid Main(string[] args)
{
for (var i = 0; i < 10; i++)
{
Test1();
Test2();
}
Console.ReadLine();
}
constint TEST_COUNT = 1000000;
staticvoid Test1()
{
Stopwatch sw = new Stopwatch();
sw.Start();
var str = "2017/08/26";
long c = 0;
for (var i = 0; i< TEST_COUNT; i++)
{
c += str.Split('/').Length;
}
sw.Stop();
Console.WriteLine($"Strong Typed: {sw.ElapsedMilliseconds:n0}ms");
}
staticvoid Test2()
{
Stopwatch sw = new Stopwatch();
sw.Start();
dynamic str = "2017/08/26";
long c = 0;
for (var i = 0; i < TEST_COUNT; i++)
{
c += str.Split('/').Length;
}
sw.Stop();
Console.WriteLine($"dynamic: {sw.ElapsedMilliseconds:n0}ms");
}
}
}
用 ildasm 反組譯工具先比較二者編譯結果的差異。
Test1 寫成 string str = "2017/08/26",MSIL 程式碼單刀直入,直接了當:
Test2 使用 dynamic str = "2017/08/26",其餘部分與 Test1 完全相同,但因為這一點差異,MSIL 程式碼截然不同,步驟多了 N 倍:
實測數據排除前面暖機階段,使用 dynamic 速度比明確宣告型別慢了約一倍。
慢一倍聽起來很恐怖,但不要忘記這畢竟是跑 100 萬次差不到 1 秒的奈米級差異,實務上對效能的影響幾可被忽略。但改幾個字元能讓程式變快又可回歸強型別檢查及 Intellisense 等諸多優勢,實在沒理由不做。在能用強型別取代 dynamic 的場合請明確宣告型別,尤其 CSHTML 使用 var 宣告變數承接 ViewBag 參數是不自覺使用 dynamic 的常見陷阱,應極力避免。