DAY 15 類別(四)

類別

如果把繼承也算上去的話,今天就是第六天的類別介紹了,在把類別講得更完整吧!

優化類別

還記得在 DAY 10 隨口提到的類別的基底類別是 object,所以我們自己做的類別,實作後也都有 object 的方法,但由於我們沒有在再定義內容,所以有些方法可能不能用,或者不如預期,所以今天來把這些方法弄完整。

那我們來做一個座標類別來示範

1
2
3
4
5
6
7
8
class Coordinate {
public Coordinate (decimal longitude, decimal latitude) {
this.longitude = longitude;
this.latitude = latitude;
}
public decimal longitude { get; }
public decimal latitude { get; }
}

ToString

1
2
3
Coordinate c1 = new Coordinate (12.156m, 7.195m);
Console.WriteLine (c1);
Console.ReadKey ();

可以看到顯示的是demo.Program+Coordinate,因為我的命名空間是 demo,所以每個人看到的可能不一樣,但這資訊都是對我們沒有幫助的,我們希望 ToString 得內容應該要能正確地獲得類別相關資訊,所以我們要

1
2
3
public override string ToString () {
return $"longitude: {longitude}, latitude: {latitude}";
}

因為 Console.WriteLine() 方法會呼叫對象的 ToString() 方法,而 object.ToString() 預設輸出類別名稱,就會發生印出不需要的內容,所以把 ToString 改成是轉出我們的相關成員的字串,之後印出這個類別就能得到有用的資訊。

GetHashCode

因為做比較時 Equals() 會警告你需先重寫 GetHashCode(),不寫不會讓程式編譯失敗,但寫好程式的幾個要點之一就是,程式執行後不要有任何的警告。

1
2
3
4
5
6
7
public override int GetHashCode () {
int hashCode = longitude.GetHashCode ();
if (longitude.GetHashCode () != latitude.GetHashCode ()) {
hashCode ^= latitude.GetHashCode ();
}
return hashCode;
}

簡單來說,只要經緯度一樣,我們就得到的 HashCode 就會是一樣,至於為什麼要用 xor(^) 來操作,因為如果是以相加減來說,較容易出現不同經緯而數值一樣的狀況,但使用 and 容易變成全是 0,or 容易全是 1,所以通常都是用 xor 來做 HashCode 的處理。

Equals

有了 GetHashCode 後,我們就可以來比較類別了。

假設我們希望兩個物件只要內容一樣就表示相等,即便參考位置不一樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Coordinate c1 = new Coordinate (12.156m, 7.195m);
Coordinate c2 = c1;
Coordinate c3 = new Coordinate (12.156m, 7.195m);
if (Coordinate.ReferenceEquals (c1, c2)) {
Console.WriteLine ("c1 與 c2 參考位置相等");
}
if (c1.Equals (c2)) {
Console.WriteLine ("c1 與 c2 相等");
}
if (Coordinate.ReferenceEquals (c1, c3)) {
Console.WriteLine ("c1 與 c3 參考位置相等");
}
if (c1.Equals (c3)) {
Console.WriteLine ("c1 與 c3 相等");
}
Console.ReadKey ();

可以看到 c1 一個也不跟 c3 相等,但我們希望 Equals() 時,顯示是 true,所以我們來改寫吧

1
2
3
4
5
6
7
public override bool Equals (object obj) {
if (obj == null)
return false;
if (this.GetType () != obj.GetType ())
return false;
return this.GetHashCode () == obj.GetHashCode ();
}

首先檢查 obj 是不是 null,再檢查型別一不一樣,然後就是用我們上面做過的方法,來確認值一不一樣。

但很多人可能使用 == 來比較多過於使用 Equals(),但你會發現 (c1 == c3) == false ,所以我們要來重寫運算子。

比較運算子

我們就把==做成跟我們 Equals() 一樣,不管參考位置

1
2
3
4
5
6
public static bool operator == (Coordinate c1, Coordinate c2) {
return c1.Equals (c2);
}
public static bool operator != (Coordinate c1, Coordinate c2) {
return !c1.Equals (c2);
}

因為重寫 == 需要連同 != 一起重寫,這樣我們就可以使用 == 來做比較了。

加法類運算子

再來就是加減的時候,能夠把經緯度相加減。

1
2
3
4
5
6
public static Coordinate operator + (Coordinate c1, Coordinate c2) {
return new Coordinate (c1.longitude + c2.longitude, c1.latitude + c2.latitude);
}
public static Coordinate operator - (Coordinate c1, Coordinate c2) {
return new Coordinate (c1.longitude - c2.longitude, c1.latitude - c2.latitude);
}

相加減後會傳回一個新的座標類別

1
2
3
4
Coordinate c1 = new Coordinate (12.156m, 7.195m);
Coordinate c2 = new Coordinate (12.06m, 1.516m);
Console.WriteLine (c1 + c2);
Console.ReadKey ();

一些小結論

今天只重寫了我覺得比較常用到的部分,還有很多很多的運算子可以重寫,就請大家自己練習了,最後附上這次完整的範例類別程式碼。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Coordinate {
public Coordinate (decimal longitude, decimal latitude) {
this.longitude = longitude;
this.latitude = latitude;
}
public decimal longitude { get; }
public decimal latitude { get; }
public override string ToString () {
return $"longitude: {longitude}, latitude: {latitude}";
}
public override int GetHashCode () {
int hashCode = longitude.GetHashCode ();
if (longitude.GetHashCode () != latitude.GetHashCode ()) {
hashCode ^= latitude.GetHashCode ();
}
return hashCode;
}
public override bool Equals (object obj) {
if (obj == null)
return false;
if (this.GetType () != obj.GetType ())
return false;
return this.GetHashCode () == obj.GetHashCode ();
}
public static bool operator == (Coordinate c1, Coordinate c2) {
return c1.Equals (c2);
}
public static bool operator != (Coordinate c1, Coordinate c2) {
return !c1.Equals (c2);
}
public static Coordinate operator + (Coordinate c1, Coordinate c2) {
return new Coordinate (c1.longitude + c2.longitude, c1.latitude + c2.latitude);
}
public static Coordinate operator - (Coordinate c1, Coordinate c2) {
return new Coordinate (c1.longitude - c2.longitude, c1.latitude - c2.latitude);
}
}

感謝閱讀。