Coding Style === ### 目的 1. 好維護 2. 增加易讀性 3. 安全 ### C# 強型別語言 ### C# & .Net 內置型別 short 與右邊一樣意思,但不要使用右邊 not System.Int16 ... ### 類別 類別成員:欄位(Field)、建構子、屬性(Property)、方法、事件、解構子(查查) ### 存取修飾詞 C#不寫存取修飾詞的話: class defalut == internal class成員 default == private 都要使用到修飾詞 internal : 包含在同一個"組件" * 查查命名空間和組件 ### Field & property #### Field 比較像類別內部的全域變數,會在物件還未執行建構子時就生成,一般在類別中都是private。 我們在取得或修改Field,都需要藉由Property來對Field做操作,不可直接操作Field。 static readonly field 非常接近於常數(const),唯一差別在於常數不需要依存在物件身上,類別撰寫編譯成功後即佔有一個記憶體空間。 #### Property Property 可以下XML註解、和可以看到有多少人參考 :::info **Field 與 Property 差異** ::: ### 數字抽出來,不要有奇怪的數字 1. 列舉(enum) 2. const 3. 區域變數 ### Don't copy 1. 冗贅 2. 維護性低 3. 不知道寫啥,複製錯 4. 懶惰了 ### 類別存放規則 1. 一個類別只放一個cs檔 2. POCO(Plain Old C#Object”或“Plain Old CLR Object) 只定義了屬性的資料類型 4. Partial類別 禁用 ### Static 程式層級 data(global) code stack heap static在創建時,即在data中創建 靜態類別無法建立Instance 全域且唯一 => 須考慮Thread safe ### 寫作邏輯 每個邏輯之間用"空行"分隔 ### 交付原則 錯誤警告訊息都為0才能交出去 ### 命名原則 1. const 一律全大寫,底線分隔 2. 區域變數、方法參數:小寫開頭,駝峰命名 3. 其餘,大寫開頭,駝峰命名 1. 方法:動詞大寫開頭,類別以及類別成員:名詞 2. bool:含有判斷意義 Is Can Has開頭 1. 類別成員屬性,不要再冠上類別名稱 2. 盡量使用有意義的變數名稱 3. linq、lambda中也盡量要有意義 4. 縮寫以大家認同的為主 5. Interface一律以I開頭,範型的型別參數一律使用"<\T>" 6. 命名空間,盡量與專案名稱和組件一致 ### 註解 多行註解以 ```csharp= // 多行註解 // 多行註解 ... ``` :::warning [What is self-documenting code and can it replace well documented code?](https://stackoverflow.com/questions/209015/what-is-self-documenting-code-and-can-it-replace-well-documented-code) * 作業:self-documenting是什麼? 不需要經由註解就能夠讓其他程式設計師理解的程式碼 * why self-documenting? 1. 只需要少量的註解, * How to self-documenting? 1. 名稱定義清楚,包含變數以及方法名稱 2. 程式撰寫邏輯拉成各個方法 3. 方法要做的事情單一化 ::: ### 工作清單 #### TODO 尚待完成的任務 #### UNDONE 還沒寫完的部分,不要有這個!!請寫完 #### HACK 目前還不好的寫法,需要修改 ### 型別 * Primitive Type基本型別 * Value Type * struct * int * char * double * Referance Type * string ### Nullable Type * null 不能被比較,若有一堆資料要找出等於10或不等於10的資料,欄位為null的欄位都不會被算進去。 * nullable type 0 與 null 的意義不同時會用到。 ``` int? a = 1; // 會轉變成參考型別 ``` * 打擊率舉例 有一位打擊者的打擊率是0跟是null,表示不一樣的事情。打擊率0表示是真滴打不到;打擊率null表示還未有此打擊者資料或遺失,不表示這位打擊者真滴打不到。 ### 浮點數誤差 10進位轉float怎麼轉,為何有誤差? 為什麼會有浮點數誤差? :::info * 作業:浮點數是怎麼呈現? [關於浮點數誤差與IEEE-754](http://davidhsu666.com/archives/ieee-754/) 根據IEEE754,一個float以4 bytes(32bits)儲存,主要分為三個部分,正負號Sign(1bit)、指數Exponent(8bits)、尾數Mantissa(23bits) 轉換流程: 1. 將10進位數字寫為2進位表示法,再將其轉為以2為底數的科學記號表示法(正規化) 2. 正負號部分(0為正、1為負)、指數部分(需要加上2進位的127,原因在於中間有8個位元來表示指數部分,原本呈現範圍應為-127-128之間,但在此要表示指數部分不需要呈現正負號,即加上127將範圍轉為0-255,用意在於將有正負的數字(-127-128)轉為不需要以正負號(0-255)呈現的方式)、尾數部分則為科學記號後的小數部分 * 誤差來源來自於"尾數"被截斷的部分, * 浮點數轉10進位 * 一些浮點數的特殊值 * Infinity * NaN * ... ::: ### 匿名型別 * 作業(好處和差異並實作) :::info * 什麼是匿名型別? 由編譯器將唯獨資料組轉換為物件的方式,型別名稱由編譯器自動賦予,其身上的屬性會由編譯器賦予合適的型別。匿名型別不可宣告於類別成員當中,即不能在物件建構的屬性、欄位中宣告。並且基於同樣原因,匿名型別只能宣告於大括號中,表示其生命週期與區域變數一致。 * 匿名型別是否能在其屬性中宣告某一屬性為null或是其他的參考型別? 無法宣告為null,但能夠宣告某一屬性變數去承接其他的參考型別,原因在於編譯器會自動去賦予合適的型別,而null並非一個型別,而是完全的空(記憶體位置指向0x0000之類的)。 ```csharp= var result = new { ID = "123", Name = "kj" }; ``` * var 與 匿名型別 因為沒有一個合適的類別來承接編譯器所自動分配的類別,因此匿名型別與var可以算是互相綁定。 ::: ### 隱含型別 :::info * 什麼是隱含型別? 使用var關鍵字宣告的變數,在編譯的時機點才會被賦予型別,事實上var是有其型別的。由編譯器來推斷最合適的型別。常用於Linq中。 唯一有一個小小小的缺點在於,因為是由編譯器自動推斷其型別,所以可能對於記憶體的控管不夠精準。 若是要給讓編譯器自動將型別推斷為某個型別,則需要將數字後面加上一些特殊字元(只限於一些基本型別)。 ```csharp= var i = 10; var j = 10l; // long // 編譯器會推斷 i 為int,但是可能事實上他只需要用short來存即可,此時即造成記憶體浪費 ``` ::: * 作業(好處和差異並實作) ### 泛型 * 作業(好處和差異並實作) :::info * 什麼是泛型? ```csharp= class TestList<T> { internal List<T> list = new List<T>(); public void Add(T value) { list.Add(value); } public T Get(int index) { return this.list[index]; } public void print() { foreach(T value in this.list) { Console.WriteLine(value.ToString()); } } } ``` * 泛型的優點? ::: ### Dynamic 編譯時期不檢查型別,脫離強型別的範疇。少用。 ### Boxing & Unboxing value type -> reference type ```csharp= int i = 123; // a value type object o = i; // boxing int j = (int)o; // unboxing ``` 配置記憶體給reference type的過程耗費效能 ### 隱含轉換 & 明確轉換 隱含轉換:不必使用特殊語法,且不會有遺失資料的可能的安全轉換 明確轉換:不安全,可能會造成資料遺失,避免 #### 正確的明確轉換寫法 * as (old,要被淘汰)不要用 * is (good) ```csharp= if (orange_1 is Fruit){ // 會做判斷傳回 boolean // do something } // 如果後面是null則為false ``` ### Overloading 同樣名稱的方法可以傳入不同參數、不同參數數量 ### 選擇性參數 原先有預設的參數,如果沒有傳入,則會採用預設值 ### 具名引數 可以在參數給予的時間點,根據特定的傳入名稱來給值,因此可以不需要依照傳入參數的順序來給予。 ### Call by value & Call by reference stack : assign值 heap : allocate記憶體空間 * ref 傳遞自己的記憶體位置進去 #### 字串不變性!!!! 因為heap中有string pool的存在!!!!! ```csharp= string name = "Jake"; func.SetValue("Tom"); func.SetValue(ref name); ``` ### Tuple [ValueTuple的東東](https://www.tutorialsteacher.com/csharp/valuetuple) ```csharp= // old Tuple<int,string, string> tuple = new Tuple<int, string, string>(1, "QQ", "SSS"); Console.WriteLine(tuple.Item1); // 1 // new (int, string, string) tuple = Tuple.Create(1, "QQ", "SSS"); ``` 效能以及可讀性較差。 ### params 可以傳入多個不特定長度的參數 少用,會喪失多載的擴充性 ```csharp= ``` ### 空字串 採用 string.Empty 不要用 "" ### @ 處理跳脫字元,有點像python中的 "r" ### 字串串接!!! string的本質是char array 直接串接會一直去尋找空的連續記憶體來存放新的字串(每串接一個新字元就成為一個新字串),如果要串接起來的字串超級長,會持續將記憶體空間佔用,限縮後來的新字串空間,因此空間會越找越久,也有可能會有記憶體不足的問題。 採用StringBuilder會更快,比較像linkedList直接指向每個字串片段的記憶體位置。 ### string format 採用 $ sign做變數與字串串接 ```csharp= string stockId = "1702"; string stockName = "南僑"; string comboBoxHeader = $"{stockId} - {stockName}"; ``` ### 字串比較 可以查找 * String.Compare() 有許多已經寫好的方法 * try一下 ### 字串尋找演算法 * 科普一下 ### 擴充方法 加上this可以擴充後面型別身上的方法 * 練習一下 ### 記憶體配置 code、data、stack、heap 32 bit & 64 bit 會影響每一個word(4 byte or 8 byte)的大小 ### 釋放資源 stack會在沒有參照後就把變數刪除 heap則不會,會持續存在於heap中,等待gc機制回收 但是gc的成本很高,若是採用dispose()的方式來回收物件,會造成效能大幅降低。 盡量避免物件建構出來沒怎麼用到就把他dispose()掉。 * 使用Stream串流來讀寫檔,記得要用using把它包起來,若是這個Stream有實作IDisposable介面,當using結束時會自動回收。直接寫的方式要避免,否則會有資源持續占用的Memory leak問題。 ### Thread Safe 不要直接開一條Thread,多利用C#的Task 多執行緒需要thread safe * Dead Lock * Static * ConcurrentDictionary ### Race condition 輸出結果由於多執行緒的關係而不一致。 ### 例外 try...catch 在編譯完成時,預期會執行到的程式碼會被存放在Cache(讀寫超快)中,但當非預期的例外產生時,需要去Ram(讀寫略慢)中找到例外處理的程式碼來執行,從Ram中拿取程式碼這個步驟很慢。 在迴圈中使用的話,會超慢,原因在於持續載入ram去找非預期的處理程序來做處理。 不要做巢狀try...catch。 catch中不准做商業邏輯,只能做處理例外狀況的處置。 finally只能做清空資源,不准做其他事情。 不要攔截自己不能處理的例外,可以拋出去給別人處理。 ### goto 會使程式碼邏輯不清楚,禁用 ### doEvent 單緒模擬多緒,禁用 ###### tags: `coding style`