1.引言
由於string使用頻繁,所以微軟把它實現像值類型那樣方便,甚至有string駐留機制,聲明相同的字符串可以指向相同的托管堆內存,這樣就可以提供內存的利用率。如果有一種class類型,它只是用來表示一些特征、一些描述信息、一些數據的存儲,但是它們聲明了就不會去改變這個對象里面的值,那么這種類型的對象就是值對象,這樣的對象就可以共享,因為它不可變。
2.駐留方式
string有駐留方式,值對象就跟值類型和string一樣,不可變,以至於值對象可以進行共享,如果值對象也能夠以駐留方式進行創建,那就可以輕松實現共享,如下例子:
public class User { public string Name { get; set; } public string Password { get; set; } }
假設上面的User是一個值對象,需求:如果User對象的屬性值相同,則保持相同的引用,如:現在要創建100個User對象,里面有99個Name和Password是相同的,那么我只要保持里面2個對象的引用即可,而不是保持100個對象的引用。通過提問,在朋友的幫助下,整理出了如下答案:
/// <summary> /// 值對象基類 /// </summary> public class ValueObject { protected static Dictionary<Dictionary<string, object>, List<Guid>> instances = new Dictionary<Dictionary<string, object>, List<Guid>>(); protected Dictionary<string, object> data = new Dictionary<string, object>(); protected Guid instanceId; protected ValueObject() { instanceId = Guid.NewGuid(); instances[data] = new List<Guid> { instanceId }; Console.WriteLine("創建對象:{0}", instanceId); } ~ValueObject() { var item = instances.SingleOrDefault(x => x.Value.Contains(instanceId)); item.Value.Remove(instanceId); if (item.Value.Count == 0) instances.Remove(item.Key); Console.WriteLine("釋放資源ID: {0}.", instanceId); Console.WriteLine("當前還有 {0}個對象", instances.Count); //System.Diagnostics.Trace.WriteLine("釋放資源ID:"+instanceId); } protected T getter<T>(string name) { T pv = default(T); var find = instances.SingleOrDefault(x => x.Value.Contains(instanceId)).Key; if (find.ContainsKey(name)) pv = (T)find[name]; return pv; } protected void setter<T>(string name, T value) { data[name] = value; reset(); } private void reset() { var query = instances.Where(w => !w.Value.Contains(instanceId)); var find = query.FirstOrDefault(f => f.Key.All(a => data.ContainsKey(a.Key) && data[a.Key] == a.Value)); if (find.Key != null) { instances.Remove(instances.FirstOrDefault(f => f.Value.Contains(instanceId)).Key); find.Value.Add(instanceId); } } }
下面看一個值對象User的實現:
public class User : ValueObject { public string Name { get { return getter<string>("Name"); } set { setter<string>("Name", value); } } public string Password { get { return getter<string>("Password"); } set { setter<string>("Password", value); } } }
調用如下:
static void Main(string[] args) { fu(); GC.Collect(); } static void fu() { List<User> list = new List<User>() { new User() { Name="qlin",Password = "1234" }, new User() { Name="qlin",Password = "1234" }, new User() { Name="lin",Password = "123456"}, new User() { Password = "12345" } }; }
為了說明,我們為User加上 判斷:
public class User : ValueObject { public string Name { get { return getter<string>("Name"); } set { setter<string>("Name", value); } } public string Password { get { return getter<string>("Password"); } set { setter<string>("Password", value); } } public override bool Equals(object obj) { if (!(obj is User)) return false; return (obj as User).Name == Name && (obj as User).Password == Password; } public override int GetHashCode() { return instances.SingleOrDefault(x => x.Value.Contains(instanceId)).Key.GetHashCode(); } public static bool operator ==(User u1, User u2) { return u1.Equals(u2); } public static bool operator !=(User u1, User u2) { return !u1.Equals(u2); } public override string ToString() { return string.Format("[name = {0}, password = {1}, instanceid = {2}, hashcode = {3}]", Name, Password, instanceId, GetHashCode()); } }
測試調用如下:
class Program { static void Main(string[] args) { fu(); GC.Collect(); } static void fu() { var u1 = new User() { Name = "qlin", Password = "1234" }; var u2 = new User() { Name = "qlin", Password = "1234" }; var u3 = new User() { Name = "lin", Password = "123456" }; var u4 = new User() { Password = "123456" }; Console.WriteLine(u1 == u2); Console.WriteLine(u1 == u3); Console.WriteLine(u3 == u4); u4.Name = "lin"; Console.WriteLine(u3 == u4); } }
3.小結
這種駐留方式創建對象,大部分系統用不上,只是提供一個示例實現而已,沒有太多的優化,但是有一些值得注意的地方
- 值對象本身只是一些數據而已,即一般都是屬性,所以就在屬性上做文章。
- 值對象創建了之后,都要緩存起來,以便以后創建相同的直接返回,查考靜態變量。
- 以前的值對象都都被緩存起來,當對象成為垃圾對象時,GC會調用對象的析構函數,通過析構函數來清理當前對象相關的緩存數據。
很多朋友提出了弱引用來創建,這是一個飛常好的方法。有啥好方法請留言,謝謝!