深入理解C#:编程技巧总结(二)
以下總結(jié)參閱了:MSDN文檔、《C#高級(jí)編程》、《C#本質(zhì)論》、前輩們的博客等資料,如有不正確的地方,請(qǐng)幫忙及時(shí)指出!以免誤導(dǎo)!
在上一篇 深入理解C#:編程技巧總結(jié)(一) 中總結(jié)了25點(diǎn),這一篇繼續(xù):
26.系列化與反系列化
使用的場(chǎng)合:
便于保存,把持有運(yùn)行狀態(tài)的對(duì)象系列化后保存到本地,在下次運(yùn)行程序時(shí),反系列化該對(duì)象來恢復(fù)狀態(tài)
便于傳輸,在網(wǎng)絡(luò)中傳輸系列化后的對(duì)象,接收方反系列化該對(duì)象還原
復(fù)制黏貼,復(fù)制到剪貼板,然后黏貼用來輔助系列化和反系列化的特性:在System.Runtime.Serialization命名空間下
OnDeserialized,應(yīng)用于某個(gè)方法,該方法會(huì)在反系列化后立即被自動(dòng)調(diào)用(可用于處理生成的對(duì)象的成員)
OnDeserializing,應(yīng)用于某個(gè)方法,該方法會(huì)在執(zhí)行反系列化時(shí)被自動(dòng)調(diào)用
OnSerialized,應(yīng)用于某個(gè)方法,對(duì)象在被系列化后調(diào)用該方法
OnSerializing,應(yīng)用于某個(gè)方法,在系列化對(duì)象前調(diào)用該方法
如果以上輔助特性仍不能滿足需求,那就要為目標(biāo)對(duì)象實(shí)現(xiàn)ISerializable接口了ISerializable接口:該接口運(yùn)行對(duì)象自己控制系列化與反系列化的過程(實(shí)現(xiàn)該接口的同時(shí)也必須應(yīng)用Serializable特性)
原理:若系列化一個(gè)對(duì)象時(shí),發(fā)現(xiàn)對(duì)象實(shí)現(xiàn)了ISerializable接口,則會(huì)忽略掉類型所有的系列化特性應(yīng)用,轉(zhuǎn)而調(diào)用類型的GetObjectData()接口方法,該方法會(huì)構(gòu)造一個(gè)SerializationInfo對(duì)象,方法內(nèi)部負(fù)責(zé)對(duì)該對(duì)象設(shè)置需要系列化的字段,然后系列化器根據(jù)該對(duì)象來系列化。反系列化時(shí),若發(fā)現(xiàn)反系列化后的對(duì)象實(shí)現(xiàn)了ISerializable接口,則反系列化器會(huì)把數(shù)據(jù)反系列化為SerializationInfo類型的對(duì)象,然后調(diào)用匹配的構(gòu)造函數(shù)來構(gòu)造目標(biāo)類型的對(duì)象。
系列化:需要實(shí)現(xiàn)GetObjectData()方法,該方法在對(duì)象被系列化時(shí)自動(dòng)被調(diào)用
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context){ }
反序列化:需要為它定義一個(gè)帶參數(shù)的受保護(hù)的構(gòu)造函數(shù),用于反系列化后重新構(gòu)造對(duì)象
protected Person(SerializationInfo info, StreamingContext context) { }
利用該接口,可以實(shí)現(xiàn)將數(shù)據(jù)流反系列化為其他任意指定對(duì)象(在原對(duì)象定義GetObjectData()方法,在目標(biāo)對(duì)象定義用于反系列化的構(gòu)造函數(shù),但兩個(gè)對(duì)象都必須實(shí)現(xiàn)ISerializable接口)
注意:
若父類為實(shí)現(xiàn)ISerializable接口,只有子類實(shí)現(xiàn)ISerializable接口,若想系列化從父類繼承的字段,則需要在子類的反系列化構(gòu)造器中和GetObjectData()方法中,添加繼承自父類的字段的處理代碼
若父類也實(shí)現(xiàn)了ISerializable接口,則只需在子類的反系列化構(gòu)造器中和GetObjectData()方法中調(diào)用父類的版本即可
不應(yīng)該被系列化的成員:
為了節(jié)省空間、流量,如果一個(gè)字段反系列化后對(duì)保存狀態(tài)無意義,就沒必要系列化它
如果一個(gè)字段可以通過其它字段推算出來,則沒必要系列化它,而用OnDeserializedAttribute特性來觸發(fā)推算方法執(zhí)行
對(duì)于私密信息不應(yīng)該被系列化
若成員對(duì)應(yīng)的類型本身未被設(shè)置為可系列化,則應(yīng)該把他標(biāo)注為不可系列化[NonSerialized],否則運(yùn)行時(shí)會(huì)拋出SerializationException
把屬性設(shè)置為不可系列化:把它的后備字段設(shè)置為不可系列化即可實(shí)現(xiàn)
把事件設(shè)置為不可系列化:[field:NonSerialized]
正常系列化與反系列化示例:自定義了工具類MyBinarySerializer
using System; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Serialization; public class Class1 {public static void Main(){Person person1 = new Person() { FirstName = "RuiFu", LastName = "Su", FullName = "Su RuiFU",IDCode="0377"};//系列化person1并存進(jìn)文件中MyBinarySerializer.SerializeToFile<Person>(person1, @"c:\", "Person.txt");//從文件中取出數(shù)據(jù)反系列化為對(duì)象(文件中不含F(xiàn)ullName信息,但系列化后自動(dòng)執(zhí)行了預(yù)定義的推算方法)Person person2 = MyBinarySerializer.DeserializeFromFile<Person>(@"c:\Person.txt");Console.WriteLine(person2.FullName);Console.ReadKey();} } [Serializable] public class Person {public string FirstName;public string LastName;[NonSerialized] //禁止被系列化public string FullName; //可被以上2個(gè)字段推算出來[OnDeserialized] //反系列化后將被調(diào)用的方法void GetFullName(StreamingContext context){FullName = string.Format("{0} {1}", LastName, FirstName);}[NonSerialized]private string idCode;public string IDCode{get{return idCode;}set{idCode = value;}}[field: NonSerialized]public event EventHandler NameChanged; } //自定義的系列化與反系列化工具類 public class MyBinarySerializer {//將類型系列化為字符串public static string Serialize<T>(T t){using(MemoryStream stream = new MemoryStream()){BinaryFormatter formatter = new BinaryFormatter();formatter.Serialize(stream, t);return System.Text.Encoding.UTF8.GetString(stream.ToArray());}}//將類型系列化為文件public static void SerializeToFile<T>(T t, string path, string fullName){if(!Directory.Exists(path)){Directory.CreateDirectory(path);}string fullPath = string.Format(@"{0}\{1}", path, fullName);using(FileStream stream = new FileStream(fullPath,FileMode.OpenOrCreate)){BinaryFormatter formatter = new BinaryFormatter();formatter.Serialize(stream, t);stream.Flush();}}//將字符串反系列化為類型public static TResult Deserialize<TResult>(string s) where TResult: class{byte[] bs = System.Text.Encoding.UTF8.GetBytes(s);using(MemoryStream stream = new MemoryStream()){BinaryFormatter formatter = new BinaryFormatter();return formatter.Deserialize(stream) as TResult;}}//將文件反系列化為類型public static TResult DeserializeFromFile<TResult>(string path) where TResult: class{using(FileStream stream = new FileStream(path,FileMode.Open)){BinaryFormatter formatter = new BinaryFormatter();return formatter.Deserialize(stream) as TResult;}} }27.異常處理:拋出異常是需要消耗性能的(但相對(duì)于低概率事件,這點(diǎn)性能影響是微不足道的)
不要利用異常處理機(jī)制來實(shí)現(xiàn)控制流的轉(zhuǎn)移
不要對(duì)能預(yù)知到的大概率、可恢復(fù)的錯(cuò)誤拋出異常,而應(yīng)該用實(shí)際代碼來處理可能出現(xiàn)的錯(cuò)誤
僅在為了防止出現(xiàn)小概率預(yù)知錯(cuò)誤、無法預(yù)知的錯(cuò)誤和無法處理的情況才嘗試拋出異常(如:運(yùn)行代碼會(huì)造成內(nèi)存泄漏、資源不可用、應(yīng)用程序狀態(tài)不可恢復(fù),則需要拋出異常)
若要把錯(cuò)誤呈現(xiàn)給最終的用戶,則應(yīng)該先捕獲該異常,對(duì)敏感信息進(jìn)行包裝后,重新拋出一個(gè)顯示友好信息的新異常
底層代碼引發(fā)的異常對(duì)于高層代碼沒有意義時(shí),則可以捕獲該異常,并重新引發(fā)意思明確的異常
在重新引發(fā)異常時(shí),總是為新異常對(duì)象提供Inner Exception對(duì)象參數(shù)(不需要提供信息時(shí)最好直接用空的throw;語句,它會(huì)把原始異常對(duì)象重新拋出),該對(duì)象保存了舊異常的一切信息,包括異常調(diào)用棧,便于代碼調(diào)試
用異常處理代替返回錯(cuò)誤代碼的方式,因?yàn)榉祷劐e(cuò)誤代碼不利于維護(hù)
千萬不要捕獲在當(dāng)前上下文中無法處理的異常,否則就可能制造了一個(gè)隱藏的很深的BUG
避免使用多層嵌套的try...catch,嵌套多了,誰都會(huì)蒙掉
對(duì)于正常的業(yè)務(wù)邏輯,使用Test-Doer模式來代替拋出異常
private bool CheckNumber(int number, ref string message) { if(number < 0) {message = "number不能為負(fù)數(shù)。";return false; } else if(number > 100) {message = "number不能大于100。";return false; } return true; } //調(diào)用: string msg = string.Empty; if(CheckNumber(59, ref msg) { //正常邏輯處理代碼 }對(duì)于try...finally,除非在執(zhí)行try塊的代碼時(shí)程序意外退出,否則,finally塊總是會(huì)被執(zhí)行
28.多線程的異常處理
在線程上若有未處理的異常,則會(huì)觸發(fā)進(jìn)程AppDomain.UnHandledException事件,該事件會(huì)接收到未處理異常的通知從而調(diào)用在它上面注冊(cè)的方法,然后應(yīng)用程序退出(注冊(cè)方法無法阻止應(yīng)用程序的退出,我們只能利用該方法來記錄日志)
在Windows窗體程序中,可以用Application.ThreadException事件來處理窗體線程中所發(fā)生的未被處理的異常,用AppDomain.UnHandledException事件來處理非窗體線程中發(fā)生的未被處理的異常。ThreadException事件可以阻止應(yīng)用程序的退出。
正常情況下,try...catch只能捕獲當(dāng)前線程的異常,一個(gè)線程中的異常只能在該線程內(nèi)部才能被捕獲到,也就是說主線程無法直接捕獲子線程中的異常,若要把線程中的異常拋給主線程處理,需要用特殊手段,我寫了如下示例代碼做參考:
static Action<Exception> action;//直接用預(yù)定義的Action委托類 static Exception exception; public static void Main() {action = CatchThreadException; //注冊(cè)方法Thread t1 = new Thread(new ThreadStart(delegate{try{Console.WriteLine("子線程執(zhí)行!");throw new Exception("子線程t1異常");}catch(Exception ex){OnCatchThreadException(ex); //執(zhí)行方法//如果是windows窗體程序,則可以直接用如下方法://this.BeginInvoke((Action)delegate//{// ? ?throw ex; //將在主線程引發(fā)Application.ThreadException//}}}));t1.Start();t1.Join();//等待子線程t1執(zhí)行完畢后,再返回主線程執(zhí)行if(exception!=null){Console.WriteLine("主線程:{0}", exception.Message);}Console.ReadKey(); } static void CatchThreadException(Exception ex) {exception = ex; } static void OnCatchThreadException(Exception ex) //定義觸發(fā)方法 {var actionCopy = action;if(actionCopy!=null){actionCopy(ex); //觸發(fā)!!!} }
29.自定義異常
僅在有特殊需要的時(shí)候才使用自定義異常
為了應(yīng)對(duì)不同的業(yè)務(wù)環(huán)境,可以在底層捕獲各種業(yè)務(wù)環(huán)境可能引發(fā)的異常(如使用不同的數(shù)據(jù)庫類型等),然后都拋出一個(gè)共同的自定義異常給調(diào)用者,這樣一來,調(diào)用者只要捕獲該自定義異常類型即可
讓自定義異常類派生自System.Exception類或其它常見的基本異常,并讓你的異常類應(yīng)用[Serializable],這樣就可以在需要的時(shí)候系列化異常(也可以對(duì)異常類實(shí)現(xiàn)ISerializable接口來自定義系列化過程)
如果要對(duì)異常信息進(jìn)行格式化,則需要重寫Message屬性
[Serializable] public class MyException : Exception {public MyException() { }public MyException(string message) : base(message) { }public MyException(string message, Exception inner) : base(message, inner) { }//用于在反系列化時(shí)構(gòu)造該異常類的實(shí)例protected MyException(SerializationInfo info,StreamingContext context): base(info, context) { } ? }
標(biāo)準(zhǔn)自定義異常類模板:(創(chuàng)建自定義異常標(biāo)準(zhǔn)類的快捷方式:在VS中輸入Exception后按Tab鍵)
30.在CLR中方法的執(zhí)行過程:
首先將參數(shù)值依次存進(jìn)內(nèi)存棧,執(zhí)行代碼的過程中,會(huì)根據(jù)需要去棧中取用參數(shù)值
遇到return語句時(shí),方法返回,并把return語句的結(jié)果值存入棧頂,這個(gè)值就是最終的返回值
若方法內(nèi)存在finally塊,則即使在try塊中有return語句,最終也會(huì)在執(zhí)行finlly塊之后才退出方法,在這種情況下,若返回值的類型為值類型,則在finally塊中對(duì)返回變量的修改將無效,方法的最終返回值都是根據(jù)return語句壓入棧頂中的值(對(duì)于引用類型,返回值只是一個(gè)引用,能成功修改該引用指向的對(duì)象,但對(duì)該引用本身的修改也是無效的),如下:
//1.值類型public static int SomeMethod(int a) {try{a = 10;return a;}finally{a = 100;Console.WriteLine("a={0}", a);} } //調(diào)用 Console.WriteLine(SomeMethod(1)); //a=100 //10 ? 這是方法的返回值,finally無法修改返回值 //2.引用類型 public class Person:ISerializable { public string FirstName; } public static Person SomeMethod(Person a) {try{a.FirstName = "Wang";return a;}finally{a.FirstName = "Su";a = null;Console.WriteLine("a={0}", a);} } //調(diào)用 Person person = new Person(); Console.WriteLine(SomeMethod(person).FirstName); ? //a= //Su ?finally成功修改了對(duì)象的字段,但對(duì)引用a本身的改變不影響返回值對(duì)象
31.線程池與線程的區(qū)別
線程:通過System.Threading.Thread類開辟的線程,用完就自行銷毀,不可重用。主要用于密集型復(fù)雜運(yùn)算
線程池:由System.Threading.ThreadPool類管理的一組線程,可重用。主要用于I/O等異步操作。線程池中的一條線程任務(wù)完成后,該線程不會(huì)自行銷毀。相反,它會(huì)以掛起狀態(tài)返回線程池。如果應(yīng)用程序再次向線程池發(fā)出請(qǐng)求,那么這個(gè)掛起的線程將激活并執(zhí)行任務(wù),而不會(huì)創(chuàng)建新線程。這節(jié)約了很多開銷。只要線程池中應(yīng)用程序任務(wù)的排隊(duì)速度低于一個(gè)線程處理每項(xiàng)任務(wù)的速度,那么就可以反復(fù)重用同一線程,從而在應(yīng)用程序生存期內(nèi)節(jié)約大量開銷。
線程池可以提供四種功能:異步調(diào)用方法、以一定的時(shí)間間隔調(diào)用方法、當(dāng)單個(gè)內(nèi)核對(duì)象得到信號(hào)通知時(shí)調(diào)用方法、當(dāng)異步 I/O 請(qǐng)求結(jié)束時(shí)調(diào)用方法
32.多線程和異步的區(qū)別
異步操作的本質(zhì):是硬件的功能,不消耗CPU資源。硬件在收到CPU的指令后,自己直接和內(nèi)存交換數(shù)據(jù),完成后會(huì)觸發(fā)一個(gè)中斷來通知操作完成(如:委托的BeginInvoke()方法,執(zhí)行該方法時(shí),在線程池ThreadPool中啟用一條線程來處理任務(wù),完成后會(huì)調(diào)用方法參數(shù)中指定的回掉函數(shù),線程池中的線程是分配好的,使用時(shí)不需要new操作)
線程的本質(zhì):是操作系統(tǒng)提供的一種邏輯功能,它是進(jìn)程中一段并發(fā)運(yùn)行的代碼,線程需要操作系統(tǒng)投入CPU資源來運(yùn)行和調(diào)度
異步操作的優(yōu)缺點(diǎn):因?yàn)楫惒讲僮鳠o須額外的線程負(fù)擔(dān),并且使用回調(diào)的方式進(jìn)行處理,在設(shè)計(jì)良好的情況下,處理函數(shù)可以不必使用共享變量(即使無法完全不用,最起碼可以減少 共享變量的數(shù)量),減少了死鎖的可能。當(dāng)然異步操作也并非完美無暇。編寫異步操作的復(fù)雜程度較高,程序主要使用回調(diào)方式進(jìn)行處理,與普通人的思維方式有些 出入,而且難以調(diào)試。
多線程的優(yōu)缺點(diǎn):多線程的優(yōu)點(diǎn)很明顯,線程中的處理程序依然是順序執(zhí)行,符合普通人的思維習(xí)慣,所以編程簡(jiǎn)單。但是多線程的缺點(diǎn)也同樣明顯,線程的使用(濫用)會(huì)給系統(tǒng)帶來上下文切換的額外負(fù)擔(dān)。并且線程間的共享變量可能造成死鎖的出現(xiàn)。
何時(shí)使用:當(dāng)需要執(zhí)行I/O操作時(shí),應(yīng)該使用異步操作。I/O操作不僅包括了直接的文件、網(wǎng)絡(luò)的讀寫,還包括數(shù)據(jù)庫操作、Web Service、HttpRequest以及.net Remoting等跨進(jìn)程的調(diào)用。而線程的適用范圍則是那種需要長(zhǎng)時(shí)間CPU運(yùn)算的場(chǎng)合,例如耗時(shí)較長(zhǎng)的圖形處理和算法執(zhí)行。
32.線程同步
線程同步:就是多個(gè)線程在某個(gè)對(duì)象上執(zhí)行等待(等待被解鎖、等待同步信號(hào)),直到該對(duì)象被解鎖或收到信號(hào)。被等待的對(duì)象必須是引用類型
鎖定:使用關(guān)鍵字lock和類型Monitor(兩者本質(zhì)上是一樣的,lock只是Monitor的語法糖),鎖定一個(gè)對(duì)象并創(chuàng)建一段代碼的塊作用域,同時(shí)只允許一個(gè)線程進(jìn)入該代碼塊,退出代碼塊時(shí)解鎖對(duì)象,后續(xù)線程按順序進(jìn)入
信號(hào)同步:涉及的類型都繼承自抽象類WaitHandle,包括Semaphore、Mutex、EventWaitHandle(子類AutoResetEvent、ManualResetEvent),他們的原理都一樣,都是維護(hù)一個(gè)系統(tǒng)內(nèi)核句柄。
EventWaitHandle維護(hù)一個(gè)由內(nèi)核產(chǎn)生的布爾變量(阻滯狀態(tài)),false表示阻塞線程,true則解除阻塞。它的子類AutoResetEvent在執(zhí)行完Set()方法后會(huì)自動(dòng)還原狀態(tài)(每次只給一個(gè)WaitOne()方法發(fā)信號(hào)),而ManualResetEvent類在執(zhí)行Set()后不會(huì)再改變狀態(tài),它的所有WaitOne()方法都能收到信號(hào)。只要WaitOne()未收到信號(hào),它就一直阻塞當(dāng)前線程,如下示例:
public static void Main() {AutoResetEvent autoReset = new AutoResetEvent(false);ManualResetEvent manualReset = new ManualResetEvent(false);Thread t1 = new Thread(new ThreadStart(() =>{autoReset.WaitOne();Console.WriteLine("線程t1收到autoReset信號(hào)!");}));Thread t2 = new Thread(new ThreadStart(() =>{autoReset.WaitOne();Console.WriteLine("線程t2收到autoReset信號(hào)!");}));t1.Start();t2.Start();Thread.Sleep(1000);autoReset.Set();//t1 t2 只能有一個(gè)收到信號(hào)Thread t3 = new Thread(new ThreadStart(() =>{manualReset.WaitOne();Console.WriteLine("線程t3收到manualReset信號(hào)!");}));Thread t4 = new Thread(new ThreadStart(() =>{manualReset.WaitOne();Console.WriteLine("線程t4收到manualReset信號(hào)!");}));t3.Start();t4.Start();Thread.Sleep(1000);manualReset.Set();//t3 t4都能收到信號(hào)Console.ReadKey(); }
34.實(shí)現(xiàn)c#每隔一段時(shí)間執(zhí)行代碼:
方法一:調(diào)用線程執(zhí)行方法,在方法中實(shí)現(xiàn)死循環(huán),每個(gè)循環(huán)用Thread.Sleep()設(shè)定阻塞時(shí)間(或用thread.Join());
方法二:使用System.Timers.Timer類;
方法三:使用System.Threading.Timer;
具體怎么實(shí)現(xiàn),就不細(xì)說了,看MSDN,或百度
?
原文地址:http://www.cnblogs.com/susufufu/p/6266216.html
.NET社區(qū)新聞,深度好文,微信中搜索dotNET跨平臺(tái)或掃描二維碼關(guān)注
總結(jié)
以上是生活随笔為你收集整理的深入理解C#:编程技巧总结(二)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MSSQL-Scripter,一个新的生
- 下一篇: 行动力决定了一个人的成败,有想法,就去做