LINQ之路 3:C# 3.0的語言功能(下)
在LINQ介紹一篇中,我們已經看到了隱式類型變量var,擴展方法(Extension method)和Lambda表達式的身影。沒錯,他們正是LINQ技術的基石,是他們讓LINQ的實現成為可能,并且簡化了LINQ表達式的書寫。在這一篇中,我將和大家一一探討C#3.0在語言功能上所作的努力,包括:擴展方法、Lambda表達式和對象初始化器。
擴展方法
下一個與LINQ密切相關的C# 3.0語言功能是擴展方法(Extension method)。在這之前,一旦一個類型被編譯進.NET程序集后,我們便不能再修改該類型的定義了。為該類型添加、修改、刪除成員的唯一辦法就是修改類型的定義代碼。
但有時候,當需要為類型添加新功能但并不擁有類型的已有代碼時,比如,我們想要為.NET庫類型List添加自定義的dump方法時,該怎么做呢,答案是擴展方法。擴展方法允許在不修改類型定義的情況下,讓該類型獲得功能上的擴展。
定義擴展方法
當定義一個擴展方法時,第一個限制就是必須把方法定義在靜態類中,因此每一個擴展方法也必須聲明為靜態的。第二個限制是擴展方法要用this關鍵字對第一個參數進行修飾,這個參數也就是我們希望進行擴展的類型。
比如下面的擴展方法允許.NET基類庫中的所有對象都擁有全新的方法DisplayDefiningAssembly()。
static class MyExtensions
{
// 本方法允許任何對象顯示它所處的程序集
public static void DisplayDefiningAssemlby(this object obj)
{
Console.WriteLine("{0} is defined in: \n\t {1}\n",
obj.GetType().Name,
System.Reflection.Assembly.GetAssembly(obj.GetType()));
}
}
調用擴展方法
我們有兩種方式來使用擴展方法,第一種是在實例層次上調用擴展方法,第二種是靜態調用擴展方法。
public void UsingExtensionMethods()
{
int myInt = 12345;
// 1. 在實例層次上調用擴展方法
myInt.DisplayDefiningAssemlby();
// 2. 靜態調用擴展方法
MyExtensions.DisplayDefiningAssemlby(myInt);
}
實例上,通過一個對象調用它的擴展方法只是編譯器的煙幕彈效果而已,背后編譯器會轉換成靜態方法的調用。
其他注意事項
上面說到,擴展方法本質上是可以從擴展類型的實例上調用的靜態方法。所以它和普通的方法是不一樣的,擴展方法不能直接訪問擴展類型的成員,從另外一個角度講,擴展方法即不是直接修改,也不是繼承。
另外一個要注意的地方是:雖然表面上擴展方法是全局的,但其實他們受制于所處的命名空間,要使用在其他命名空間中定義的擴展方法時,我們首先需要導入該命名空間。
Lambda表達式
Lambda表達式的引入是與委托類型的使用密切相關的,本質上,Lambda表達式只是用更簡單的方式來書寫匿名方法,從而徹底簡化.NET委托類型的使用。下面我們一步一步的來看看Lambda表達式的簡化之路:
實例找出整數List<T>中的偶數,我們調用了List<T>類型的FindALl()方法,這個方法需要System.Predicate<T>泛型委托,它用于接受類型為T的輸入參數并返回一個布爾值。
傳統的委托使用方式
傳統的委托使用方式會為委托目標定義一個單獨的方法,如下:
public static void TraditionalDelegateSyntax()
{
List<int> list = new List<int>();
list.AddRange(new int[] { 1, 5, 10, 20 ,33 });
//使用傳統委托語法調用FindAll
Predicate<int> callback = new Predicate<int>(IsEvenNumber);
List<int> evenNumbers = list.FindAll(callback);
foreach (int num in evenNumbers)
Console.Write("{0}\t", num);
//Output: 10 20
}
// Predicate<>委托的目標
static bool IsEvenNumber(int i)
{
return (i % 2) == 0;
}
匿名方法取代顯示的委托函數
這種方式讓我們不再需要完整的方法定義,對于一些專門為了委托而定義的函數而言是一個很大的簡化,如下:
public static void AnonymousMethodSyntax()
{
List<int> list = new List<int>();
list.AddRange(new int[] { 1, 5, 10, 20, 33 });
//使用匿名方法
List<int> evenNumbers = list.FindAll(
delegate(int i)
{
return (i % 2) == 0;
});
foreach (int num in evenNumbers)
Console.Write("{0}\t", num);
//Output: 10 20
}
Lambda表達式
Lambda表達式讓我們進一步簡化FindAll()的調用,使用新的語法時,底層的委托語法消失得無影無蹤,如下所示:
public static void LambdaExpressionSyntax()
{
List<int> list = new List<int>();
list.AddRange(new int[] { 1, 5, 10, 20, 33 });
//使用Lambda表達式
List<int> evenNumbers = list.FindAll(i => (i % 2) == 0);
foreach (int num in evenNumbers)
Console.Write("{0}\t", num);
//Output: 10 20
}
Lambda表達式可以應用于任何匿名方法可以應用的場合,而且比匿名方法更加簡潔更節省編碼時間。其實C#編譯器只是把Lambda表達式翻譯為相應的普通匿名方法而已。
Lambda表達式的格式:先定義參數列表,”=>”標記(可讀為:goes to)緊隨其后,然后是表達式。即:ArgumentsToProcess => StatementsToProcessThem
Lambda表達式的參數可以是顯示類型化的也可以是隱式類型化的。比如上例中的參數i就是隱式類型化的,我們也可以寫為如下:
// 顯示定義參數的類型
List<int> evenNumbers = list.FindAll((int i) => (i % 2) == 0);
Lambda表達式也可以是一個代碼塊,其中包含多條代碼語句,用花括號括起來即可:
// 使用語句塊編寫Lambda表達式
List<int> evenNumbers = list.FindAll((int i) =>
{
Console.WriteLine("processing value: {0}", i);
bool isEven = (i % 2) == 0;
return isEven;
});
對象初始化器
C# 3.0提供的 對象初始化器語法用來初始化新類或新結構變量的狀態。使用這種語法,我們可以以一種非常簡潔的方式來創建對象和為對象的屬性賦值。如下:
public class Point
{
public Point() { }
public Point(int x, int y)
{
X = x;
Y = y;
}
public int X { get; set; }
public int Y { get; set; }
}
static void ObjectInitSyntax()
{
// 手動初始化各屬性
Point aPoint = new Point();
aPoint.X = 10;
aPoint.Y = 20;
// 使用新的對象初始化語法進行初始化
Point bPoint = new Point { X = 10, Y = 20 };
}
使用初始化語法調用構造函數
上面的示例中,對象初始化語法會隱式調用默認的構造函數初始化Point實例,而且我們還可以顯示調用定制的構造函數,如下:
static void ObjectInitSyntax()
{
// 在這里,默認構造函數被隱式調用
Point bPoint = new Point { X = 10, Y = 20 };
// 我們也可以顯示調用默認構造函數
Point cPoint = new Point() { X = 10, Y = 20 };
// 我們還可以調用自定義的構造函數,只是這里1, 2會被10, 20覆蓋
Point dPoint = new Point(1, 2) { X = 10, Y = 20 };
}
初始化內部類型
當我們用這種語法來初始化一個“復雜”的對象時,其優點會更具說服力,假如我們有類Rectangle如下,可以明顯的看出,對象初始化語法不但大大減少了我們敲打鍵盤的次數,也更加的簡潔明了。
public class Rectangle
{
public Point TopLeft { get; set; }
public Point BottomRight { get; set; }
}
static void CompareObjectInitMethods()
{
// 傳統初始化方法
Rectangle r = new Rectangle();
Point p1 = new Point();
p1.X = 10;
p1.Y = 10;
r.TopLeft = p1;
Point p2 = new Point();
p2.X = 20;
p2.Y = 20;
r.BottomRight = p2;
// 對象初始化語法
Rectangle r2 = new Rectangle
{
TopLeft = new Point { X = 10, Y = 10 },
BottomRight = new Point { X = 20, Y = 20 }
};
}
集合的初始化
集合初始化語法非常類似于對象初始化語法,它使得我們可以像初始化普通數組一樣初始化容器(如ArrayList或List<T>)。
static void CollectionInitSyntax()
{
// 初始化標準數組
int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// 初始化一個ArrayList
ArrayList list = new ArrayList { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// 初始化一個List<T>泛型容器
List<int> list2 = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// 如果容器存放的是非簡單對象
List<Point> pointList = new List<Point>
{
new Point { X = 2, Y = 2},
new Point { X = 3, Y = 3}
};
// 使用恰當的縮進和嵌套的大括號會使代碼易于閱讀,同時節省我們的輸入時間
// 想想如果不使用初始化語法構造如下的List,將需要多少行代碼
List<Rectangle> rectList = new List<Rectangle>
{
new Rectangle { TopLeft = new Point { X = 1, Y = 1},
BottomRight = new Point { X = 2, Y = 2}},
new Rectangle { TopLeft = new Point { X = 3, Y = 3},
BottomRight = new Point { X = 4, Y = 4}},
new Rectangle { TopLeft = new Point { X = 5, Y = 5},
BottomRight = new Point { X = 6, Y = 6}}
};
}

浙公網安備 33010602011771號