程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#5新特性詳解之二——方法調用和Lambda表達式

C#5新特性詳解之二——方法調用和Lambda表達式

編輯:C#入門知識

C# 5中對於異步編程的支持毫無疑問是第一大新特性,這個在我上周的文章http://www.mindscapehq.com/blog/index.php/2012/03/13/asynchronous-programming-in-c-5/ 裡已經寫過了。不過還有一些其他C#使用者在意的新特性我覺得也有必要提一下。

方法調用信息

有一個非常全面的企業程序設計指南說道:如果你使用VB編程,你會習慣於使用日志記錄每一個被調用的方法:

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中引入了“可選參數”(optional parameters)這一概念,也就是說,方法即使不使用參數,編譯器也會給它賦上默認值。

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

也就是說,如果調用Log.Trace("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接口時,並不需要使用字符串、正則表達式匹配或者奇怪的結構(mystic weavers)。

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]獲取調用函數的行號,這對於診斷方法非常有用。不過如果你真的需要它,那也許會是太過“企業化”的現象。

在Lambda表達式中使用循環變量

技術上來說,這是對一個長期存在的困惑和痛苦的問題修復。它使得C#更具可用性,所以無論如何我都需要提到它。

從C# 3開始,得利於Lambda語法的使用,匿名函數變得比命名函數更加快速和簡單。匿名函數在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語法裡使用循環變量時它會給你錯誤的結果——它是在循環的初始值而不是終值。

例如:下面是一個返回一系列'adder'值的方法,每一個'adder'對應每一個輸入的加數。

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));
}
 
// Printout: 15 15 15 15 15
很明顯,這是一個嚴重錯誤!每個返回的函數都是加到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修復了這一問題,你將可以獲得預期的結果。如果你想利用C#的混合面向對象函數特性,它為你避免了一個多年來一直在制造問題的大陷阱。

從語言的角度來看,C# 5並沒有很多需要學習的新功能,雖然異步編程和等待關鍵字確實是兩個非常大的突破。編程快樂! 


作者 王然

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved