C# 5.0中新增特性

C# 5.0隨着VisualStudio 2012一塊兒正式發佈了,讓咱們來看看C#5.0中增長了哪些功能。php

1. 異步編程web

在.Net 4.5中,經過async和await兩個關鍵字,引入了一種新的基於任務的異步編程模型(TAP)。在這種方式下,能夠經過相似同步方式編寫異步代碼,極大簡化了異步編程模型。以下式一個簡單的實例:編程

    static async void DownloadStringAsync2(Uri uri)
    {
        var webClient = new WebClient();
        var result = await webClient.DownloadStringTaskAsync(uri);
        Console.WriteLine(result);
    }

 

而以前的方式是這樣的:app

    static void DownloadStringAsync(Uri uri)
    {
        var webClient = new WebClient();
        webClient.DownloadStringCompleted += (s, e) =>
            {
                Console.WriteLine(e.Result);
            };
        webClient.DownloadStringAsync(uri);
    }

 

也許前面這個例子不足以體現async和await帶來的優越性,下面這個例子就明顯多了:less

   

 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     }

 

關於基於任務的異步編程模型須要介紹的地方還比較多,不是一兩句能說完的,有空的話後面再專門寫篇文章來詳細介紹下。另外也可參看微軟的官方網站:Visual Studio Asynchronous Programming,其官方文檔Task-Based Asynchronous Pattern Overview介紹的很是詳細, VisualStudio中自帶的CSharp Language Specification中也有一些說明。異步

2. 調用方信息async

不少時候,咱們須要在運行過程當中記錄一些調測的日誌信息,以下所示:異步編程

    public void DoProcessing()
    {
        TraceMessage("Something happened.");
    }

 

爲了調測方便,除了事件信息外,咱們每每還須要知道發生該事件的代碼位置以及調用棧信息。在C++中,咱們能夠經過定義一個宏,而後再宏中經過__FILE__和__LINE__來獲取當前代碼的位置,但C#並不支持宏,每每只能經過StackTrace來實現這一功能,但StackTrace卻有不是很靠譜,經常獲取不了咱們所要的結果。函數

針對這個問題,在.Net 4.5中引入了三個Attribute:CallerMemberName、CallerFilePath和CallerLineNumber。在編譯器的配合下,分別能夠獲取到調用函數(準確講應該是成員)名稱,調用文件及調用行號。上面的TraceMessage函數能夠實現以下:oop

  

  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);
    }

 

另外,在構造函數,析構函數、屬性等特殊的地方調用CallerMemberName屬性所標記的函數時,獲取的值有所不一樣,其取值以下表所示:

調用的地方

CallerMemberName獲取的結果

方法、屬性或事件

方法,屬性或事件的名稱

構造函數

字符串 ".ctor"

靜態構造函數

字符串 ".cctor"

析構函數

該字符串 "Finalize"

用戶定義的運算符或轉換

生成的名稱成員,例如, "op_Addition"。

特性構造函數

特性所應用的成員的名稱

例如,對於在屬性中調用CallerMemberName所標記的函數便可獲取屬性名稱,經過這種方式能夠簡化 INotifyPropertyChanged 接口的實現。關於調用方信息更詳細的資料,請參看MSDN:http://msdn.microsoft.com/zh-cn/library/hh534540.aspx

方法調用信息

這是一個被寫在Writting Enterprisey Code上的完整風格指南,可是其中我最喜歡的是迷人對你調用過的全部函數的日誌記錄:

Function AddTwoNumbers(a As Integer, b As Integer) As Integer
  Logger.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 removed
  Logger.Trace("ArithmeticHelpers", "AddTwoNumbers", "Returned from PrintPurchaseOrders")
  Logger.Trace("ArithmeticHelpers", "AddTwoNumbers", "Exiting AddTwoNumbers")
  Return result
End Function

即便這段代碼用企業級標準編寫得有效而清晰,而使用C# 5可使它更加高效和清晰。C# 4推薦使用可選的參數,這意味着方法調用者能夠不用考慮參數,編譯器將會用默認值填充。

public void WonderMethod(int a = 123, string b = "hello") { ... }
  
WonderMethod(456);  // compiles to WonderMethod(456, "hello")
WonderMethod();     // compiles to WonderMethod(123, "hello")

有了C# 5,你能夠將一個特殊屬性放置在可選參數上,編譯器將會使用調用方法的信息填充變量而不是使用某個常量。這意味着咱們可以實現Logger.Trace,來自動收集它是從哪裏調用的信息:

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);
}

如今,若是調用LogTrace("some message"),編譯器將會不會用空字符串填充而是使用文件以及調用所發生的成員:

// In file Validation.cs
public void ValidateDatabase() {
  Log.Trace("Entering method");
  // compiles to Log.Trace("Entering method", "Validation.cs", "ValidateDatabase")
  Log.Trace("Exiting method");
}

請注意這些你爲參數設置的屬性必須是可選的,若是不是可選的,C#編譯器將須要調用代碼主動提供,而且提供的值必須覆蓋默認值。

另外一個你怎樣使用這個的例子即是實現INotifyPropertyChanged,不須要逐字的匹配字符串,表達式:

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]獲得調用代碼的行號,這個也許對診斷方法有用,可是若是你真的須要它,這也許是這段調用代碼太過「企業化」的跡象。

在lambdas中使用循環變量

技術上,這個對長期存在的困擾和煎熬的修正,可是使得C#增長了可用性,因此我將會說起它。

自從C# 3以來,編寫匿名函數比命名的更見快捷和容易了,匿名函數被普遍地使用魚LINQ,可是他們也在其餘狀況下被使用,如你想要在不須要受權的巨大層級類和接口以及可見函數中快速的擁有參數化行爲。匿名函數的一個重要特性就是你能夠從本地環境中捕獲變量,如下是一個示例:

public static IEnumerable<int> GetGreaterThan(IEnumerable<int> source, int n) {
  return source.Where(i => i > n);
}

看這裏,i=>i>n是一個捕獲了n值的匿名函數。例如若是n是17,那麼該函數即是 i=>i>17

 

在C#以前的版本中,若是你編寫了一個循環,你不能在lambda中使用循環變量。事實上,它比想象中更糟。你能夠在lambda中使用循環變量,可是它將給你一個錯誤的結果。它會使用循環退出時的玄幻變量值,不是唄捕獲時的值。

例如,下面是一個返回「加法器」集合的函數:

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));
}

很明顯這大錯特錯!在返回的集合中的每一個函數都在捕獲5做爲加數後結束。這是由於爲他們結束在循環變量,加數,而後最終的循環變量值爲5。

 

要想在C# 3和4中使用這些,你須要記住將循環變量拷貝至一個局部變量中,而後用你的lambda覆蓋局部變量:

foreach (var addend_ in addends) {
  var addend = addend_;  // DON'T GO NEAR THE LOOP VARIABLE
  funcs.Add(i => i + addend)
}

因爲這些函數是被局部變量覆蓋而不是用循環變量,這些值如今被保存,你便能得到真確的值。

 

以此種方式並非一種模糊的邊緣狀況,我在個人項目中碰到過不少次。有一個來自某個項目中的更加現實的例子即是構建一個用來過濾的函數,這個函數是構建自被用戶指定的約束對象集合。該代碼循環處理約束對象,並構建表明子句的函數列表(如 Name Equals "BOB" 變成 r =>r["Name"]=="BOB"),而後將這些函數混合至一個最終的過濾器中,該過濾器運行這全部的子句而後檢查他們是否是爲真。我第一次運行沒有成功由於每隔子句函數以相同的約束對象覆蓋--集合中的最後一個。

在C# 5中,這些修復以及你能夠覆蓋的循環變量能使你得到你指望的結果。

via:mindscapehq.com , OSChina原創編譯

相關文章
相關標籤/搜索