標籤:
前言
一般來說,實值型別存於棧,參考型別存在於堆,實值型別轉化為參考型別叫Box, 參考型別轉為實值型別在Unbox, 最近看了一本書發現實值型別與參考型別的知識遠不止這些。
我發現在一下幾點我的理解一直是錯誤的:
錯誤1. struct是實值型別,它與System.Object沒有任何關係。
struct 直接基類是System.ValueType, 而它的基類就是Sysem.Object, 而且int, bool,等基本類型都是struct. 所以所有的C#類型都繼承自System.Object.
錯誤2. 實值型別轉換為介面類型不會裝箱
我們都知道object obj = 3 會裝箱; 其實IEquatable<int> obj = 3; 和前面的一樣也會裝箱。
錯誤3. struct類型的equals 不會裝箱。
不僅有裝箱,而且還有兩次!
錯誤4:裝箱,拆箱的記憶體分布。
裝箱後,會重新分配堆,除了相關的值,還有函數表指標等,記憶體對齊等,還有一個漏掉的是指向這個裝箱後對象的引用。
本文
namespace ConsoleApplication1{ // System.ValueType -> System.Object struct Point2D { public int X { get; set; } public int Y { get; set; } // version 1 public override bool Equals(object obj) { if (!(obj is Point2D)) return false; Point2D other = (Point2D)obj; return X == other.X && Y == other.Y; } // version 2 public bool Equals(Point2D other) { return X == other.X && Y == other.Y; } // version 3 public static bool operator ==(Point2D a, Point2D b) { return a.Equals(b); } public static bool operator !=(Point2D a, Point2D b) { return !(a == b); } } struct Point2D_V4 : IEquatable<Point2D_V4> { public int X { get; set; } public int Y { get; set; } // version 1 public override bool Equals(object obj) { if (!(obj is Point2D_V4)) return false; Point2D_V4 other = (Point2D_V4)obj; return X == other.X && Y == other.Y; } // version 2 public bool Equals(Point2D_V4 other) { return X == other.X && Y == other.Y; } // version 3 public static bool operator ==(Point2D_V4 a, Point2D_V4 b) { return a.Equals(b); } public static bool operator !=(Point2D_V4 a, Point2D_V4 b) { return !(a == b); } } public class Employee { public string Name { get; set; } // the hashcode is base on the its cotent public override int GetHashCode() { return Name.GetHashCode(); } } class Program { static void Main(string[] args) { // version 1 Point2D a = new Point2D(), b = new Point2D(); //object test = b; // box b from value type to reference type //a.Equals(b); // box a to invoke the virtual function Equals // if we have 10,000,000 points, we will do 20,000,000 box operation, on 32-bit system, one box will allocate 16 Bytes. // version 2 // How to avoid boxing override the equals. //a.Equals(b); // no need box a or b this time. // version 3 //bool isequal = a == b;// no boxing here. // it seems ok now, but there is a edge case will cause boxing at CLR generic. we need implement Iequatable //Point2D_V4 i = new Point2D_V4(), j = new Point2D_V4(); //IEquatable<Point2D_V4> k = i;// box occurs here, value type to interface requires boxing. //i.Equals(j);// no box occurs // once boxing, they are not have relationship to orignal type. so the best practice is let the value type immutable, like system.datetime. //Point2D_V4 point = new Point2D_V4 { X = 5, Y = 7 }; //Point2D_V4 anotherPoint = new Point2D_V4 { X = 6, Y = 7 }; //IEquatable<Point2D_V4> equatable = point; //boxing occurs here //equatable.Equals(anotherPoint); //returns false //point.X = 6; //point.Equals(anotherPoint); //returns true //equatable.Equals(anotherPoint); //returns false, the box was not modified! // then we need to talk about the hashcode, if you know about the hashkey in datastructure. the hashcode is used for identity one or more than one items. // here are some requirements to the hashcode function: //1. if two item is equal, the hashkey must be equal. //2. if two item is equal, the hashkey should not equal, but not must. //3. the GetHashCode function should fast. //4. the Hashcode should not change. HashSet<Employee> employees = new HashSet<Employee>(); Employee kate = new Employee { Name = "Kate Jones" }; employees.Add(kate); kate.Name = "Kate Jones-Smith"; // it really shock me, I don‘t notice this before. bool has = employees.Contains(kate); //returns false! } }}
version 1:
public override bool Equals(object obj)
首先參數ojbect 會做一次裝箱,然後調用Equals 由於是虛函數,而實值型別沒有函數指標表,無法調用虛函數,必須轉為參考型別,才能調用,我用ILDasm的確可以看到做了Box.
Version 2:
當我們重載了Equals函數,可以避免調用虛函數,這樣可以避免兩次裝箱。
version 3:
讓比較更加完善,對== , != 進行重載。
version 4:
繼承自IEquatable, 在CLR generics 中會用到。需要進一步的驗證。
version 5:
對HashCode的設計做了一些建議。不要依賴於可變的東西,否則正如上面例子所示範的,潛在的不穩定性。
總結
1. 使用實值型別,當我們會建立大量的執行個體,比如10,000,000
2. override Equals, oeverload Equals, implement IEquatable<T> , overload ==, !=,
3. override GetHashCode
4. 使實值型別為不變
參考
<<Pro .Net Performance>>
C# 實值型別 與參考型別