C# 5.0中新增特性
C# 5.0隨著VisualStudio 2012一起正式發(fā)布了,讓我們來看看C#5.0中增加了哪些功能。
1. 異步編程
在.Net 4.5中,通過async和await兩個關(guān)鍵字,引入了一種新的基于任務(wù)的異步編程模型(TAP)。在這種方式下,可以通過類似同步方式編寫異步代碼,極大簡化了異步編程模型。如下式一個簡單的實(shí)例:
static async void DownloadStringAsync2(Uri uri){var webClient = new WebClient();var result = await webClient.DownloadStringTaskAsync(uri);Console.WriteLine(result);}?
而之前的方式是這樣的:
static void DownloadStringAsync(Uri uri){var webClient = new WebClient();webClient.DownloadStringCompleted += (s, e) =>{Console.WriteLine(e.Result);};webClient.DownloadStringAsync(uri);}?
也許前面這個例子不足以體現(xiàn)async和await帶來的優(yōu)越性,下面這個例子就明顯多了:
? ?
1 public void CopyToAsyncTheHardWay(Stream source, Stream destination) 2 { 3 byte[] buffer = new byte[0x1000]; 4 Action<IAsyncResult> readWriteLoop = null; 5 readWriteLoop = iar => 6 { 7 for (bool isRead = (iar == null); ; isRead = !isRead) 8 { 9 switch (isRead) 10 { 11 case true: 12 iar = source.BeginRead(buffer, 0, buffer.Length, 13 readResult => 14 { 15 if (readResult.CompletedSynchronously) return; 16 readWriteLoop(readResult); 17 }, null); 18 if (!iar.CompletedSynchronously) return; 19 break; 20 case false: 21 int numRead = source.EndRead(iar); 22 if (numRead == 0) 23 { 24 return; 25 } 26 iar = destination.BeginWrite(buffer, 0, numRead, 27 writeResult => 28 { 29 if (writeResult.CompletedSynchronously) return; 30 destination.EndWrite(writeResult); 31 readWriteLoop(null); 32 }, null); 33 if (!iar.CompletedSynchronously) return; 34 destination.EndWrite(iar); 35 break; 36 } 37 } 38 }; 39 readWriteLoop(null); 40 } 41 42 public async Task CopyToAsync(Stream source, Stream destination) 43 { 44 byte[] buffer = new byte[0x1000]; 45 int numRead; 46 while ((numRead = await source.ReadAsync(buffer, 0, buffer.Length)) != 0) 47 { 48 await destination.WriteAsync(buffer, 0, numRead); 49 } 50 }?
關(guān)于基于任務(wù)的異步編程模型需要介紹的地方還比較多,不是一兩句能說完的,有空的話后面再專門寫篇文章來詳細(xì)介紹下。另外也可參看微軟的官方網(wǎng)站:Visual Studio Asynchronous Programming,其官方文檔Task-Based Asynchronous Pattern Overview介紹的非常詳細(xì), VisualStudio中自帶的CSharp Language Specification中也有一些說明。
2. 調(diào)用方信息
很多時候,我們需要在運(yùn)行過程中記錄一些調(diào)測的日志信息,如下所示:
public void DoProcessing(){TraceMessage("Something happened.");}?
為了調(diào)測方便,除了事件信息外,我們往往還需要知道發(fā)生該事件的代碼位置以及調(diào)用棧信息。在C++中,我們可以通過定義一個宏,然后再宏中通過__FILE__和__LINE__來獲取當(dāng)前代碼的位置,但C#并不支持宏,往往只能通過StackTrace來實(shí)現(xiàn)這一功能,但StackTrace卻有不是很靠譜,常常獲取不了我們所要的結(jié)果。
針對這個問題,在.Net 4.5中引入了三個Attribute:CallerMemberName、CallerFilePath和CallerLineNumber。在編譯器的配合下,分別可以獲取到調(diào)用函數(shù)(準(zhǔn)確講應(yīng)該是成員)名稱,調(diào)用文件及調(diào)用行號。上面的TraceMessage函數(shù)可以實(shí)現(xiàn)如下:
??
public void TraceMessage(string message,[CallerMemberName] string memberName = "",[CallerFilePath] string sourceFilePath = "",[CallerLineNumber] int sourceLineNumber = 0){Trace.WriteLine("message: " + message);Trace.WriteLine("member name: " + memberName);Trace.WriteLine("source file path: " + sourceFilePath);Trace.WriteLine("source line number: " + sourceLineNumber);}?
另外,在構(gòu)造函數(shù),析構(gòu)函數(shù)、屬性等特殊的地方調(diào)用CallerMemberName屬性所標(biāo)記的函數(shù)時,獲取的值有所不同,其取值如下表所示:
| 調(diào)用的地方 | CallerMemberName獲取的結(jié)果 |
| 方法、屬性或事件 | 方法,屬性或事件的名稱 |
| 構(gòu)造函數(shù) | 字符串 ".ctor" |
| 靜態(tài)構(gòu)造函數(shù) | 字符串 ".cctor" |
| 析構(gòu)函數(shù) | 該字符串 "Finalize" |
| 用戶定義的運(yùn)算符或轉(zhuǎn)換 | 生成的名稱成員,例如, "op_Addition"。 |
| 特性構(gòu)造函數(shù) | 特性所應(yīng)用的成員的名稱 |
例如,對于在屬性中調(diào)用CallerMemberName所標(biāo)記的函數(shù)即可獲取屬性名稱,通過這種方式可以簡化?INotifyPropertyChanged?接口的實(shí)現(xiàn)。關(guān)于調(diào)用方信息更詳細(xì)的資料,請參看MSDN:http://msdn.microsoft.com/zh-cn/library/hh534540.aspx。
方法調(diào)用信息
這是一個被寫在Writting Enterprisey Code上的完整風(fēng)格指南,但是其中我最喜歡的是迷人對你調(diào)用過的所有函數(shù)的日志記錄:
Function AddTwoNumbers(a As Integer, b As Integer) As IntegerLogger.Trace("ArithmeticHelpers", "AddTwoNumbers", "Entering AddTwoNumbers")Dim result = OracleHelpers.ExecInteger("SELECT " & a & " + " & b)Logger.Trace("ArithmeticHelpers", "AddTwoNumbers", "Calling PrintPurchaseOrders")PrintPurchaseOrders() ' IFT 12.11.96: don't know why this is needed but shipping module crashes if it is removedLogger.Trace("ArithmeticHelpers", "AddTwoNumbers", "Returned from PrintPurchaseOrders")Logger.Trace("ArithmeticHelpers", "AddTwoNumbers", "Exiting AddTwoNumbers")Return result End Function即使這段代碼用企業(yè)級標(biāo)準(zhǔn)編寫得有效而清晰,而使用C# 5可以使它更加高效和清晰。C# 4推薦使用可選的參數(shù),這意味著方法調(diào)用者可以不用考慮參數(shù),編譯器將會用默認(rèn)值填充。
public void WonderMethod(int a = 123, string b = "hello") { ... }WonderMethod(456); // compiles to WonderMethod(456, "hello") WonderMethod(); // compiles to WonderMethod(123, "hello")有了C# 5,你可以將一個特殊屬性放置在可選參數(shù)上,編譯器將會使用調(diào)用方法的信息填充變量而不是使用某個常量。這意味著我們能夠?qū)崿F(xiàn)Logger.Trace,來自動收集它是從哪里調(diào)用的信息:
public static void Trace(string message, [CallerFilePath] string sourceFile = "", [CallerMemberName] string memberName = "") {string msg = String.Format("{0}: {1}.{2}: {3}",DateTime.Now.ToString("yyyy-mm-dd HH:MM:ss.fff"), // Lurking 'minutes'/'months' bug introduced during .NET port in 2003 and has not been noticed because nobody ever looks at the log files because they contain too much useless detail Path.GetFileNameWithoutExtension(sourceFile),memberName,message);LoggingInfrastructure.Log(msg); }現(xiàn)在,如果調(diào)用LogTrace("some message"),編譯器將會不會用空字符串填充而是使用文件以及調(diào)用所發(fā)生的成員:
// In file Validation.cs public void ValidateDatabase() {Log.Trace("Entering method");// compiles to Log.Trace("Entering method", "Validation.cs", "ValidateDatabase")Log.Trace("Exiting method"); }請注意這些你為參數(shù)設(shè)置的屬性必須是可選的,如果不是可選的,C#編譯器將需要調(diào)用代碼主動提供,并且提供的值必須覆蓋默認(rèn)值。
另一個你怎樣使用這個的例子便是實(shí)現(xiàn)INotifyPropertyChanged,不需要逐字的匹配字符串,表達(dá)式:
public class ViewModelBase : INotifyPropertyChanged {protected void Set<T>(ref T field, T value, [CallerMemberName] string propertyName = "") {if (!Object.Equals(field, value)) {field = value;OnPropertyChanged(propertyName);}}// usual INPC boilerplate }public class Widget : ViewModelBase {private int _sprocketSize;public int SprocketSize {get { return _sprocketSize; }set { Set(ref _sprocketSize, value); } // Compiler fills in "SprocketSize" as propertyName } }很值得的是,你也可以使用[CallerLineNumber]得到調(diào)用代碼的行號,這個也許對診斷方法有用,但是如果你真的需要它,這也許是這段調(diào)用代碼太過“企業(yè)化”的跡象。
在lambdas中使用循環(huán)變量
技術(shù)上,這個對長期存在的困擾和煎熬的修正,但是使得C#增加了可用性,所以我將會提及它。
自從C# 3以來,編寫匿名函數(shù)比命名的更見快捷和容易了,匿名函數(shù)被廣泛地使用魚LINQ,但是他們也在其他情況下被使用,如你想要在不需要授權(quán)的巨大層級類和接口以及可見函數(shù)中快速的擁有參數(shù)化行為。匿名函數(shù)的一個重要特性就是你可以從本地環(huán)境中捕獲變量,以下是一個示例:
public static IEnumerable<int> GetGreaterThan(IEnumerable<int> source, int n) {return source.Where(i => i > n); }看這里,i=>i>n是一個捕獲了n值的匿名函數(shù)。例如如果n是17,那么該函數(shù)便是 i=>i>17
?
在C#之前的版本中,如果你編寫了一個循環(huán),你不能在lambda中使用循環(huán)變量。事實(shí)上,它比想象中更糟。你可以在lambda中使用循環(huán)變量,但是它將給你一個錯誤的結(jié)果。它會使用循環(huán)退出時的玄幻變量值,不是唄捕獲時的值。
例如,下面是一個返回“加法器”集合的函數(shù):
public static List<Func<int, int>> GetAdders(params int[] addends) {var funcs = new List<Func<int, int>>();foreach (int addend in addends) {funcs.Add(i => i + addend);}return funcs; } var adders = GetAdders(1, 2, 3, 4, 5); foreach (var adder in adders) {Console.WriteLine(adder(10)); }很明顯這大錯特錯!在返回的集合中的每個函數(shù)都在捕獲5作為加數(shù)后結(jié)束。這是因?yàn)闉樗麄兘Y(jié)束在循環(huán)變量,加數(shù),然后最終的循環(huán)變量值為5。
?
要想在C# 3和4中使用這些,你需要記住將循環(huán)變量拷貝至一個局部變量中,然后用你的lambda覆蓋局部變量:
foreach (var addend_ in addends) {var addend = addend_; // DON'T GO NEAR THE LOOP VARIABLEfuncs.Add(i => i + addend) }由于這些函數(shù)是被局部變量覆蓋而不是用循環(huán)變量,這些值現(xiàn)在被保存,你便能獲得真確的值。
?
以此種方式并不是一種模糊的邊緣情況,我在我的項(xiàng)目中碰到過很多次。有一個來自某個項(xiàng)目中的更加現(xiàn)實(shí)的例子便是構(gòu)建一個用來過濾的函數(shù),這個函數(shù)是構(gòu)建自被用戶指定的約束對象集合。該代碼循環(huán)處理約束對象,并構(gòu)建代表子句的函數(shù)列表(如 Name Equals "BOB" 變成 r =>r["Name"]=="BOB"),然后將這些函數(shù)混合至一個最終的過濾器中,該過濾器運(yùn)行這所有的子句然后檢查他們是不是為真。我第一次運(yùn)行沒有成功因?yàn)槊扛糇泳浜瘮?shù)以相同的約束對象覆蓋--集合中的最后一個。
在C# 5中,這些修復(fù)以及你可以覆蓋的循環(huán)變量能使你獲得你期望的結(jié)果。
via:mindscapehq.com?,?OSChina原創(chuàng)編譯
轉(zhuǎn)載于:https://www.cnblogs.com/ctcx/p/5177635.html
總結(jié)
以上是生活随笔為你收集整理的C# 5.0中新增特性的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 利用GDataXML解析XML文件
- 下一篇: 数据库中字段类型对应的C#中的数据类型