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`