我犯了一个错误,您能指出吗?(结论)
其實許多朋友已經在回復中發現問題所在了,其中最早指出錯誤的是狼Robot同學,他說:
每個T都會使用一個新的連接。
泛型類中的靜態變量會因為T的不同而產生不同的值,也就是說每個T所訪問的靜態變量都是獨立的。
正是這個原因,導致UserRepository和ArticleRepository,雖然似乎都繼承了Repository<T>類,但是因為使用了不同的T類型,所以實際上它們是不同的類,而它們的ConnectionKey值是不同的。使用不同的ConnectionKey,就無法從ResourceManager中獲得同一個Connection對象了。以下的代碼可以很輕易地證明這一點。
public static class MyClass<T> {public static readonly Guid Key = Guid.NewGuid(); }class Program {static void Main(string[] args){Console.WriteLine("int: " + MyClass<int>.Key);Console.WriteLine("string: " + MyClass<string>.Key);Console.ReadLine();} }由于MyClass<int>和MyClass<string>是不同的類,因此它們的Key也是分離的,值也不同(Guid.NewGuid()了兩次)。因此,在泛型類中定義靜態字段的時候一定要注意:不同泛型參數生成的具體類(無論是值類型還是引用類型),它們的靜態字段是獨立的。這點說起來簡單,但是有時候不太容易意識到。例如,我之所以犯這個錯誤,正是因為原本Repository類是非泛型的,而后面由于某些原因才將其改為泛型。這樣的錯誤使用單元測試也很難檢查出來,非常隱蔽。
不過解決方案也非常簡單,例如隨意給出一個具體的Guid,而不是每次都使用Guid.NewGuid生成新的值:
public abstract class Repository<T> {private readonly static Guid ConnectionKey = new Guid("a18b2f49-cafc-43e3-a49d-3fac91701394"); }這樣,雖然UserRepostory和ArticleReposityr的ConnectionKey還是不同的Guid對象,但是它們的“值”是相同的(也就是說GetHashCode相同,Equals返回true),對字典來說它們是相同的“鍵”。當然,還有其他解決方案,例如把ConnectionKey放到其它非泛型的類中去即可。
有些朋友還提出了其他的觀點。例如,ResourceManager是不同的實例,怎么做到“保留Connection對象”呢?其實只需要它們都基于一個合適的數據容器就可以了,比如都基于HttpContext.Current。這方面的例子很多,比如不同的Connection對象都是訪問同一個數據庫的。因此,這里不是問題。
還有,有朋友認為共享Connection對象的做法不好。其實這也是沒有關系的,因為這里“共享”的范圍只是“單個請求”。對于ASP.NET請求來說,這些操作都是同步的,因此不會產生線程安全的問題。而一個請求的時間很短,因此Connection的生命周期也不長。這樣的實踐很多,例如NHiberante推薦為每個請求分配一個唯一的ISession對象(Sharp Architecture就是怎么做的)——這就相當于一個Connection——不過我不喜歡,因此我使用的做法是為單個請求按需創建多個Session,但是共享一個Connection對象。此外,共享Connection對象還有其他一些好處,例如不會引發需要MSDTC的分布式事務。
這個問題已經解決了。但是上文的評論中還有其他一些討論。例如,您知道為什么下面的代碼中,兩個時間是相同的嗎?
public static class MyClass<T> {public static readonly DateTime Time = DateTime.Now; }class Program {static void Main(string[] args){Console.WriteLine("int: " + MyClass<int>.Time);Thread.Sleep(3000);Console.WriteLine("string: " + MyClass<string>.Time);Console.ReadLine();} }它們輸出的結果是:
int: 2009/9/8 15:30:06 string: 2009/9/8 15:30:06這和我們的理解好像不同,因為當我們訪問MyClass<string>的時候,應該比MyClass<int>要晚3秒鐘,但為什么時間是相同的呢?那么我們把測試代碼換一種寫法,會更清楚一些:
public static class MyClass<T> {public static readonly DateTime Time = GetNow();private static DateTime GetNow(){Console.WriteLine("GetNow execute!");return DateTime.Now;} }class Program {static void Main(string[] args){Console.WriteLine("Main execute!");Console.WriteLine("int: " + MyClass<int>.Time);Thread.Sleep(3000);Console.WriteLine("string: " + MyClass<string>.Time);Console.ReadLine();} }我們增加了會輸出一些內容的GetNow靜態方法,Main方法的開頭也打印出一些內容。這段代碼輸出如下:
GetNow execute! GetNow execute! Main execute! int: 2009/9/8 15:34:31 string: 2009/9/8 15:34:31可以發現,在Main方法執行之前,MyClass<int>和MyClass<string>的GetNow就被調用了。因此,它們的Time字段是相同的。不過,如果我們在MyClass<>中增加一個空的靜態構造函數,結果就會有所不同:
public static class MyClass<T> {public static readonly DateTime Time = GetNow();private static DateTime GetNow(){Console.WriteLine("GetNow execute!");return DateTime.Now;}static MyClass() { } }輸出如下:
Main execute! GetNow execute! int: 2009/9/8 15:40:12 GetNow execute! string: 2009/9/8 15:40:15由于GetNow方法只在“第一次”用到MyClass<int>和MyClass<string>時執行,因此獲得的時間是不同的。不過,為什么加入了靜態構造函數之后,Time字段的初始化時機就有所改變呢?那是因為IL中beforefieldinit修飾在作怪。關于這一點,許多書中都有提及。園子中的Artech同學對這個問題也有所分析。
在目前的情況下,泛型類這一性質給我們造成了一定的麻煩。但是,只要我們使用得當,它也可以在某些場景下簡化開發。因此,最后請大家和我一起在心中默念:信腦袋,得永生,信腦袋,得永生……
from: http://blog.zhaojie.me/2009/09/i-made-a-mistake-can-you-figure-it-out-answer.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的我犯了一个错误,您能指出吗?(结论)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我犯了一个错误,您能指出吗?
- 下一篇: 您能看出这个生成缩略图的方法有什么问题吗