不如來編碼-luminjis web
本期話題:
1:使用屬性還是字段
首先重大區別就是屬性實質是方法,所以:
1:可以為屬性添加代碼;
2:可以讓屬性支持線程安全;見effective c#第一版的第一章;
3:屬性得到了VS編輯器的支持,得以實現自動屬性這種功能。
4:自動屬性的特點在LINQ中得到了廣泛應用,尤其是匿名類型中,只能實現只讀的自動屬性,匿名類型不支持字段;
5:從設計的角度,也就是面向對象的角度,建議使用屬性;
6:如果某個屬性僅僅作為類型內部使用,而且不涉及到上面5點內容,則建議使用字段
還有一點,屬性在序列化中有點尴尬,由於屬性是方法,所以不能為其指定[NonSerialized]特性。
2:利用泛型拓寬方法的應用范圍
假設存在方法:
static void PrintSalary(ISalary<Employee> s){ s.Pay();}
然後像下面這樣使用它:
ISalary<Programmer> s = new BaseSalaryCounter<Programmer>();PrintSalary(s);
注意,幾個主要類的代碼:
interface ISalary<T>{ void Pay();}class BaseSalaryCounter<T> : ISalary<T>{ public void Pay() { Console.WriteLine("Pay base salary"); }}class Employee{ public string Name { get; set; }}class Programmer : Employee{}
運行結果是:
無法從“ConsoleApplication4.ISalary<ConsoleApplication4.Programmer>”轉換為“ConsoleApplication4.ISalary<ConsoleApplication4.Employee>”
顯然我們認為PrintSalary方法因為能支持ISalary<Employee> ,所以肯定能支持ISalary<Programmer>,這是一種很容易犯的嘗試錯誤。
改進方法先提供一種思路,就是將該方法改成泛型。
3:利用協變關鍵字out
要解決話題2中的問題,還可以使用out關鍵字。即修改接口為:
interface ISalary<out T>{ void Pay();}
4:減少使用類型的靜態變量
1:靜態變量一旦被創建不被釋放;
2:靜態變量不是線程安全的,它不像類型變量只對創建類型的那個線程有效;
5:線程同步有多少種方法?
所謂線程同步,采用的技術,就是在某個對象上等待(也理解為鎖定該對象)。C#中類型對象的分類:引用類型和值類型。在這兩種類型上的等待是不一樣的,我們也可以簡單的理解為在CLR中,值類型是不能被鎖定的(參考MSDN的volatile關鍵字)。
在引用類型上的等待,又分為兩種技術:鎖定和信號機制。
鎖定使用:關鍵字lock和類型Monitor,前者其實是後者的語法糖。這是最常用的同步技術,小伙們應該都用過;
信號機制:信號機制中涉及的類型都繼承自抽象類WaitHandle,這些類型有EventWaitHandle(類型化為AutoResetEvent、ManualResetEvent)和Semaphore以及Mutex。它們之間有一定的區別,值得一提的是Mutex能同步不同應用程序域。其它幾個類型的不同點MSDN寫的很傷人,而且未進行橫向比較,簡單來說,就是EventWaitHandle通過布爾型進行同步,其中AutoResetEvent又自動恢復類型的阻滯狀態,ManualResetEvent則必須手動恢復阻滯狀態。Semaphore通過整型進行同步。
6:讓對象=null能否加速對象被垃圾回收器回收?
注意,以下描述都只針對引用類型
1:在方法內,方法參數和局部變量,賦值為null無助於加速被垃圾回收器標識為垃圾;
2:實例變量,隨著實例=null,實例變量也會自動=null。所以,一般情況下,無需顯式為實例變量=null,除非你的實例生存周期較長,並且實例變量是個大對象;
3:靜態變量,如果不顯式置為null,就永遠不會被回收;
7:不建議lock(this)
1:如果兩個對象的實例分別執行了lock(this)這樣的代碼,實際鎖定的是兩個對象,完全不能達到同步的目的。
2:最好避免鎖定 public 類型或鎖定不受應用程序控制的對象實例。例如,如果該實例可以被公開訪問,則 lock(this) 可能會有問題,因為不受控制的代碼也可能會鎖定該對象。這可能導致死鎖,即兩個或更多個線程等待釋放同一對象。出於同樣的原因,鎖定公共數據類型(相比於對象)也可能導致問題。
8:區分計算密集型和I/O密集型的多線程應用場景
I/O密集型操作。硬盤、網卡、聲卡、顯卡等都是。CLR所提供的異步編程模型就是讓我們充分利用硬件的DMA功能來提高CPU的利用率。
一個在大多數情況下正確的技巧是,凡是FCL中類型提供了類似BeginDoSomething方法的,都建議使用這個異步調用來完成多線程編碼,異步在後台調用線程池線程完成調度,最大化的節約了系統的性能。
9:使用Parallel的一個陷阱
以下的代碼的輸出是什麼?
int[] nums = new int[] { 1, 2, 3, 4 };long total = 0;Parallel.For<long>(0, nums.Length, () =>{ return 1;}, (i, loopState, subtotal) =>{ subtotal += nums[i]; return subtotal;}, (x) => Interlocked.Add(ref total, x));Console.WriteLine("The total is {0}", total);
實際上,它有可能是11,較少的情況下會是12,幾乎不可能出現13,14。
要從方法的最後一個參數Action<TLocal> localFinally講起,它表示的其實是並行中如果新起了一個線程,那麼這個線程結束時會調用這個localFinally。 而我們知道並行在後台采用的是線程池,也就是,我們不知道上面代碼所發起的這個並行實際會發起多少個線程,如果是1個,自然就是11,如果是2,結果自然就是12了,依此類推。之所以不可能為14,是因為,並發的循環體就只有4個循環,並發顯然不會傻到新起4個線程來執行並發。
10:不建議lock(類型的type)
由於typeof(SampleClass),是SampleClass的所有實例所共有的,這會導致當前應用程序中的所有SampleClass的實例的線程,將會全部被同步。