文中充滿了各種C#與其他語言的對比及吐槽, 希望介意者勿觀… 當然, 鑒于太亂, 我懷疑有沒有人能看完.
C#也的確不是當年那種微軟, Windows獨占的語言了,mono項目已經將C#移植到了Mac和linux上, 甚至還包括iOS和Android. 也就是說, 假如你愿意的話, 你可以使用C#通吃所有平臺, 當然, 前提是你能接受巨貴的授權費用. 作為什么事情都喜歡自己搞一套的微軟,(因為他都是壟斷的) 在C#這件事情上一開始就很開放(因為有java在前), 總算是做對了一件事情.
Mono在創建一個名為test的console的工程后, 給了我一個Hello World的代碼,
using System;namespace test{ class MainClass { public static void Main (string[] args) { Console.WriteLine ("Hello World!"); } }}第一眼看去, 就知道C#中了JAVA的毒了, 用所謂完全面向對象的方式, 強迫你寫一堆臃腫而無用的代碼… 我至今也沒有明白為什么Main函數最后會變成Main類, 也沒有明白這有什么好處… 想起最近看的一篇文章Why Java Sucks and C# Rocks, 說”自從C# 1.0誕生之日起,就只出現Java借鑒C#特性的情況”, 以此來駁斥JAVA一派對C#抄襲的指責. 其實, 光是從Hello World都能看出來C#對JAVA的模仿, 他的言論也的確是避重就輕了, 因為, 連他也無法否認C#誕生前及誕生過程中發生的事情…
值得一提的是, C#對傳統的PRintf進行了改進,{0}形式的占位符不需要表明類型, 只按照數量和位置匹配.
type name;的形式.auto的類型推理關鍵字var. 可以極大的簡化我們的生活~~~大家都知道類似C/C++, JAVA, C#這種對效率還稍微有些追求的語言都是靜態類型語言, 并且靠編譯期靜態類型檢查來排查錯誤, 而從C++以后, 各語言都以更加’真正的’強類型自豪. 而類似auto,var等自動類型推導的變量只不過是語法糖而已, 所以當我看到C#的確提供了動態類型dynamic, 我還是著實吃了一驚. 這也體現了C#的設計者們比C++, JAVA更加激進的一面. 很多年前, BS就說過(其實Mats也說過類似的), 語言不是一堆特性的堆積, 也不是說堆積的越多就越好. 因為, 很多時候JAVA, C#的擁護者們炫耀著JAVA, C#有著什么樣的新特性的時候, 其實并不太感冒. 但是, 這一個, 夠讓人震撼的. 一個靜態語言里面有動態類型會是什么效果? 不禁想讓人嘗試一番.
public static dynamic Add(dynamic var1, dynamic var2){ return var1 + var2;}類似上面的Add功能, 本來必須使用模版才能實現,var使用時, 因為是類型推導, 所以必須在初始化時才能使用, 不能用于函數參數, 而真正的動態類型dynamic就可以. 最近使用Python, Ruby比較多, 突然感覺C#有種Python, Ruby上身的感覺.
當我看到Why Java Sucks and C# Rocks(2):基礎類型與面向對象一文時, 我還以為C#真的已經是所謂的”完全面向對象”了. 所以當我看到C#入門經典書中寫到變量的類型還是分為引用類型和值類型時, 我相當意外.(我先看的那一系列文章, 再看的C#入門經典) 等我看到書中寫到封箱和拆箱的時候, 就更加驚訝了… 都是一個對象, 為啥還要封箱和拆箱呢? 當然, 鑒于Objective-C連自動的封箱和拆箱都還沒有, 我也不能說這就有多么落后.不過, 我用C++的時候就完全沒聽說過封箱和拆箱的概念, 容器的設計完全可以容納基礎數值, 為啥到了Objective-C, JAVA和C#里反而不行了呢? 因為C++沒有統一的基類? 容器設計的時候就壓根不是光考慮存儲啥Object對象的. 這個倒是讓我想起一句話, 你以為你解決了一個問題, 因為你比以前更加優美了, 但是同時帶來了另外一個問題, 后來, 你們比較的是后一個問題誰更優美的解決了. 而這個問題本來并不存在…另外, 當你其實還分值類型和引用類型的時候, 你就已經輸給Python, Ruby了, 何必還討論誰的箱子更好看呢… 要把這個問題上升到理念層次, 我更加就沒法認同了.
增加了checked, unchecked(默認)關鍵字來應付類型轉換時的溢出問題. 比如下面的代碼:
short source = 257;byte dest = (byte)source;
上面的代碼在轉換時會發生溢出, 這往往不是我們要的結果, 也往往因此出現莫名而難以調試的bug. 而類似下面的代碼會在運行時會拋出System.OverflowException: Number overflow.異常. 這個方案很值得欣賞. 簡單有效.
System.IndexOutOfRangeException, 這似乎是C++以后語言的標配了.ref, 也算結束了JAVA中痛苦的經歷. 在JAVA中都是pass by value, 連一個簡單的swap函數都不能直接實現. 還得通過一個構建一個數組來實現, 都不知道怎么想的.out, 與ref類似, 有以下區別:實際上就等于C++里面的參數默認值, 在有默認值時, 在函數最后的參數為可選. 看書中說C#是在C#4后才支持, 為啥呢?
命名參數在動態語言里面是很常見的, 并且是個容易理解又很使用的功能. 但是C++并沒有支持, 看BS在C++語言的設計與演化中講到其實當時有過討論, 只是因為在C++中有所謂的接口與實現分離, 而看以前的代碼, 很多接口(頭文件)使用的參數名和實現中用的參數名并不一樣, 所以這個有用的特性并沒有加入C++. 這也是為什么我前面說接口與實現分離實在是太不DRY的一個原因.當時BS給的例子是用Win32 API創建windows的代碼(因為書在同事那里, 記憶不準確請指出), 因為需要的參數實在是太多了. 非常的不方便, 見MSDN:
HWND WINAPI CreateWindow( _In_opt_ LPCTSTR lpClassName, _In_opt_ LPCTSTR lpWindowName, _In_ DWord dwStyle, _In_ int x, _In_ int y, _In_ int nWidth, _In_ int nHeight, _In_opt_ HWND hWndParent, _In_opt_ HMENU hMenu, _In_opt_ HINSTANCE hInstance, _In_opt_ LPVOID lpParam );
事實上, 在Win32 API里面, 你要完整的創建一個窗口, 還有類似注冊窗口類等巨多參數的接口, 而其實在這個API里面, 每次調用時真正需要使用的又并不是所有的參數, 不用說有多不方便了. 可選參數(參數默認值), 只能在參數列表的最后使用, 讓這種簡化有的時候變成了一個排序游戲, 到底哪個參數才是最不常用的呢?BS給了在C++里面我們的一種解決方案, 這種方案也是我們在實際中使用的方案, 那就是用struct, 當struct成員變量都有默認值的時候, 我們就只需要給我們真正需要的那個變量賦值即可. 具體的情況就不多說了, 書上都有, 但是在有命名參數后這些都是浮云. 你只需要給你的確需要的參數賦值即可, 也不需要額外的創建類或結構. 比如上例, 有了命名參數后, 我假如只對窗口的寬度感興趣, 那么如下調用即可:
public static int CreateWindow ( int lpClassName = 0, int lpWindowName = 0, int dwStyle = 0, int x = 0, int y = 0, int nWidth = 0, int nHeight = 0, int hWndParent = 0, int hMenu = 0, int hInstance = 0, int lpParam = 0){ return 0;} public static void Main (string[] args){ CreateWindow(nWidth: 640, nHeight: 960);}還有比這更方便的事情嗎? 順面吐槽一句, Objective-C里面函數調用的方式簡直就是為命名參數準備的, 當然竟然完全不支持命名參數, 甚至不支持參數默認值, 崩潰啊…
這算是接觸到的第一個較新的概念, 多寫一點.delegate是Objective-C里面用的非常多的概念, 有很方便的一面, 但是是在類這個層次上的概念.C#的委托更加想是Objective-C的SEL/@selector和C++ 11的function, 也就是為了方便函數調用(特別是回調函數)和構建高階函數而存在的. 這個也是函數不是第一類值(first class)的語言里面需要解決的問題. 函數指針有人說很方便, 但是那個語法實在太逆天了. 當然, 因為這個原因, 其實C#的委托也無法實現直接對<, >, +, -等操作符的控制, 而是需要用類似C++的方法提供輔助函數的方法來完成.
C#的委托:
using System;namespace test{ class MainClass { delegate int Actor (int leftParam, int rightParam); static int Call (Actor fun, int leftParam, int rightParam) { return fun(leftParam, rightParam); } static int Multiply (int leftParam, int rightParam) { return leftParam * rightParam; } static int Divide (int leftParam, int rightParam) { return leftParam / rightParam; } public static void Main (string[] args) { Console.WriteLine ("{0}", Call(new Actor(Multiply), 10, 10)); Console.WriteLine ("{0}", Call(new Actor(Divide), 10, 10)); Console.ReadKey(); } }}有匿名函數的語言才算是現代語言啊… 我說這句話, C++, JAVA, Objective-C, C#無一中槍, 不管是加入的早晚(其實都是較晚), 上述語言都已經有了使用匿名函數的辦法. 對于Python, Ruby來說, 匿名函數就更不是什么新鮮事物了. 比較有意思的是, 作為靜態語言的新事物, 上述語言都獨立的發展了一套自己的Lambda語法, 而且各有特色, 并且最終的目的似乎都是讓你搞不明白. C#的Lambda使用了=>來標記. 因為可以使用類型推導, 所以語法的簡潔性上可以做到極致.參考上面委托的例子, 假如Multiply和Divide我們只是使用一次的話, 還按上面的形式定義就太麻煩了, 匿名函數可以簡化代碼.
class MainClass{ delegate int Actor (int leftParam, int rightParam); static int Call (Actor fun, int leftParam, int rightParam) { return fun(leftParam, rightParam); } public static void Main (string[] args) { Console.WriteLine ("{0}", Call((leftParam, rightParam) => { return leftParam * rightParam; }, 10, 10)); Console.WriteLine ("{0}", Call((leftParam, rightParam) => { return leftParam / rightParam; }, 10, 10)); Console.ReadKey(); }}可以看到代碼顯著的簡化, 當然, 其實這里的例子還是太過簡單和生造了, 匿名函數最大的應用在于利用閉包特性來作為回調函數. 此時簡化的往往不僅僅是一個函數, 甚至是一套完整的類. 而且, 在多個類似回調同時在一個類中使用的時候, 每個匿名函數各自獨立, 不會出現需要在類的回調函數中用switch/if-else區分的丑陋代碼. 這個回憶回憶以前你用過的任何GUI系統的Button回調, 大概就能理解. 而最佳的例子, 我也常常提起我很感嘆的是, 在最近的Objective-C中加入的匿名函數(在Objective-C中被稱為Block)對Objective-C接口的巨大影響, 幾乎是整體性的對原有delegate的替換. 這個替換過程不僅僅發生在社區, 連Apple官方也是如此.
傳統的try, catch, finally模式.
主要有價值的特性都在這一部分. 我覺得最大的改進就在于C#和JAVA都沒有使用C++(還有Objective-C)里面看似優美的接口與實現分離的策略. 很多時候我們都在說DRY(Don’t Repeat Yourself)是編程中排在第一的原則, 但是接口與實現分離, 即一個頭文件用于聲明, 一個實現文件用于實現的方式是徹頭徹尾的Repeat. 這種方式就我了解是來自于C語言. C++和Objective-C都在一定程度上有向C語言兼容的負擔, 于是都這樣做了. 稍微追求點人性化的語言其實都不是類似C/C++和Objective-C那種方式. 這個序列可以從JAVA, C#一直寫到lua, python, ruby, lisp.這里有個值得探討的話題
新聞熱點
疑難解答