C#是一種功能強大的語言,但並不是所有程序員都熟悉我們將在本書中討論的所有功能。因此, 本章將介紹優秀的Web窗體程序員需要了解的C#語言功能。
本章僅簡要介紹每一項功能。有關C#語言本身的知識不是本課程講述的重點(本課程重點講述Asp.net技術)。讀者需要在學習本門課程之外,利用空余時間去學習C#語言基礎知識。推薦幾本參考書籍:
- 《C#4.0圖解教程》:https://yunpan.cn/ck7CeQGc5naFD (提取碼:ffc7)
- 《C#編程語言與面向對象基礎精簡教程》:https://yunpan.cn/ck7hugfgDPPHi (提取碼:6b42
1.創建示例項目
為了演示語言功能,本節創建了一個名為LanguageFeatures的新的ASP.NET Empty Web Application (ASP.NET空Web應用)項目。 為了演示不同的功能,在項目中添加了一個Web窗體,以便添加新的Default.aspx文件,該文件的內容如代碼清單3-1所示。
代碼清單3-1 Default.aspx文件的初始內容
代碼為該頁面添加了一個標題,並用一些標准HTML元素替換了默認的窗體條目,這包括一個p 元素,其中包含插入計算GetMessage方法的結果的代碼片段。在Default.aspx.cs代碼隱藏文件中定義了 GetMessage方法,該文件的內容如代碼清單3-2所示。
這是一個非常簡單的起點。啟動應用程序后,Web窗體生成的HTML如圖3-1所示。
思考: 1.如果在Default.aspx.cs文件中沒有定義GetMessage方法,運行Default.aspx頁面會不會出錯? 2.如果Default.aspx.cs代碼修改成如下代碼: using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Text; using System.Threading.Tasks; namespace LanguageFeatures { public partial class Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } } public class MyDefault { protected string GetMessage() { return "Hello,This is a Web Form"; } } } 此時訪問Default.aspx頁面,是否會出錯?為什么?
2.使用自動實現的屬性
屬性是C#類中的一種成員,有關屬性的介紹請參考:http://www.runoob.com/csharp/csharp-property.html。
下面要講述的是如何實現自動屬性和為什么要使用自動屬性。
在 LanguageFeatures項目中添加一個Product類,該類中包含了一個簡單的示例,如代碼清單3-3所示。 (要創建新類,請在Solution Explorer中右鍵單擊LanguageFeatures項,然后從彈出式菜單中選擇Add →Class。將名稱設置為Product.cs,然后單擊Add按鈕創建該文件。)
這個Name屬性以粗體顯示。將在讀取該屬性值時執行get代碼塊(稱為getter)中的語句,在為該 屬性分配值(特殊變量value表示分配的值)時執行set代碼塊(稱為setter)中的語句。 屬性將由其他類使用,就好像它是一個字段一樣,如代碼清單3-4所示,它演示了如何使用 Default.aspx.cs代碼隱藏類中的屬性。
如上所示,代碼讀取並設置了屬性值,就像它是一個常規字段一樣。使用屬性要優於使用字段, 因為你可以更改get和set代碼塊中的語句,而無需更改所有依賴該屬性的類。 啟動項目,即可看到此示例的效果。由於僅使用了Default.aspx Web窗體來顯示GetMessage方法返 回的字符串,因此將以文本而不是屏幕截圖的形式顯示結果。啟動應用程序后,瀏覽器顯示的消息如 下所示:
Product name:Kayak
很好!但是,如果類包含許多屬性,所有這些屬性都只是間接訪問某個字段,那么代碼就會變得 冗長起來,最終導致代碼過於臃腫,看看代碼清單3-5就知道了:
使用屬性是一個不錯的做法,因為隨后你可能需要更改獲取和設置值的方式,但這種靈活性也生 成了大量低效、難以閱讀的代碼。解決這個問題的辦法是使用自動實現屬性,也稱為自動屬性。使用 自動屬性,可以擺脫冗余代碼,即創建一種支持字段的屬性模式,如代碼清單3-6所示。
在使用自動屬性時,需要注意幾個要點。首先,不必定義getter和setter的主體。其次,不必定義 支持屬性的字段。這兩項內容將在構建類時由C#編譯器定義。自動屬性與常規屬性並無不同,在代碼 清單3-4中,在代碼隱藏類里的代碼仍然有效,無需任何修改。 使用自動屬性,減少了輸入量,創建了更易於閱讀的代碼,並且仍然保留了屬性提供的靈活性。 如果需要更改實現屬性的方式,則可以返回到常規屬性格式。假設需要更改處理Name屬性的方式,如 代碼清單3-7所示。

必須實現getter和setter才能返回到常規屬性。C#不支持在一個屬性中混合使用自動格式和常規格式的getter和setter。
3.使用對象和集合初始化器
另一個繁瑣的任務是構建一個新對象,然后單獨為它的屬性分配值,如代碼清單3-8所示,其中顯示了對Default.aspx.cs代碼隱藏類所做的更改。
必須完成3個步驟才能創建Product對象並生成結果:創建對象,設置參數值,然后返回希望插入 到由Default.aspx Web窗體返回的HTML中的值。可以使用C#對象初始化器功能,在單個步驟中創建並填充Product實例,從而減少這其中的一個步驟,如代碼清單3-9所示。
Product名稱調用后的大括號({})即為初始化器,用來在構造過程中為參數提供值。每個分配以 逗號隔開,並且不需要以任何方式給正為其分配值的屬性名添加前綴。(換言之,為ProductID而不是 myProduct.ProductID分配值。) 與自動屬性很像,這是一項由編譯器實現的功能,它使C#代碼更易於閱讀和編寫。如果啟動應用 程序,將在瀏覽器中看到以下消息:
Category: Watersports
該功能可以在構造過程中初始化集合和數組的內容,如代碼清單3-10所示。
代碼清單演示了如何構造和初始化泛型集合庫中的一個數組和兩個類。運行該示例,瀏覽器中將 顯示以下消息:
Fruit:orange
這項功能只是強化了語法——提高了C#的易用性,沒有任何其他影響。
4.使用擴展方法
擴展方法可以向不為你所有或無法直接修改的類中添加方法。這里在新類文件ShoppingCart.cs中定義了ShoppingCart類(購物車類),該類的內容如代碼清單3-11所示。ShoppingCart表示Product對象的集合。
using System.Collections.Generic; using System.Collections; namespace LanguageFeatures { public class ShoppingCart : IEnumerable<Product> { public List<Product> Products { get; set; } } }
這是一個非常簡單的類,它對Product對象的List進行了包裝。(本例中,只需要一個基本類。)假 設需要確定ShoppingCart類中Product對象的總值,但由於該類來自第三方並且手頭沒有源代碼,因而 無法修改該類,這時可以使用擴展方法獲得所需的功能。代碼清單3-12顯示了MyExtensionMethods類, 它是在MyExtensionMethods.cs這個新類文件中定義的。
擴展方法必須為靜態,並且必須在靜態類中定義。第一個參數前面的this關鍵字將TotalPrices 標注為擴展方法。第一個參數告訴.NET可以將擴展方法應用於哪個類——本例中為ShoppingCart。 cartParam參數可以引用已對其應用擴展方法的ShoppingCart的實例。TotalPrices方法將枚舉 ShoppingCart 中 的 Product ,並返回 Product.Price 屬性的總和。代碼清單 3-13 顯示了如何在 Default.aspx.cs代碼隱藏類中應用擴展方法。
注意:擴展方法不允許打破類為其方法、字段和屬性定義的訪問規則。你可以使用擴展方法擴展某個類的功能,但只能使用有權訪問的類成員。
其中的關鍵語句如下所示:
decimal cartTotal = cart.TotalPrices();
如你所見,代碼對ShoppingCart對象調用了TotalPrices方法(實際上是其他類定義的擴展方法), 就好像它是ShoppingCart類的一部分。如果擴展類處在相關作用域內,也就是說,擴展類和調用擴展 方法的類屬於同一命名空間,或處在using語句主體的命名空間中,則.NET將會查找擴展類。以下是 結果:
Total:$378.40
這里使用了因位置而異的貨幣字符串格式,它會顯示本地貨幣符號以及金額。這意味着,將根據 你所在的位置顯示結果,你可能看不到顯示的美元符號。
5.將擴展方法應用於接口
還可以創建應用於接口的擴展方法,這樣就可以對實現接口的所有類調用擴展方法。有關接口的定義和基本用法請參考:http://www.runoob.com/csharp/csharp-interface.html。將擴展方法應用於接口的代碼如代碼清單3-14 ,其中更新了ShoppingCart類,以實現IEnumerable接口:
代碼清單3-14在ShoppingCart類中實現接口
using System.Collections.Generic; using System.Collections; namespace LanguageFeatures { public class ShoppingCart : IEnumerable<Product> { public List<Product> Products { get; set; } public IEnumerator<Product> GetEnumerator() { return Products.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } }
現在,可以更新擴展方法,以便處理IEnumerable,如代碼清單3-15所示,其中顯示了 MyExtensionMethods類的更改情況。
將參數類型更改為IEnumerable,這意味着方法主體中的foreach循環將直接作用於 Product對象(第4小節中是作用於cartParam.Products)。將參數更愛為接口,意味着可以計算由任何IEnumerable返回的Product對象的總值, 這包括ShoppingCart的實例以及Product對象的數組,如代碼清單3-16所示。
代碼清單3-16 將擴展方法應用於同一接口的不同實現
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Text; using System.Threading.Tasks; namespace LanguageFeatures { public partial class Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } protected string GetMessage() { IEnumerable<Product> products = new ShoppingCart { Products = new List<Product> { new Product {Name = "Kayak", Category = "Watersports", Price = 275M}, new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M}, new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M}, new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M} } }; Product[] productArray = { new Product {Name = "Kayak", Category = "Watersports", Price = 270M}, new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M}, new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M}, new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M} }; decimal cartTotal = products.TotalPrices(); decimal arrayTotal = productArray.TotalPrices(); return String.Format("Cart Total:{0:c},Array Total:{1:c}", cartTotal, arrayTotal); } } }
輸出的網頁上顯示結果為:
Cart Total:¥378.40,Array Total:¥373.40
這表示無論以何種方式收集Product對象,擴展方法都會正確調用TotalPrices()方法返回正確結果。
6.使用 lambda 表達式
委托(delegate)可以針對每個Product調用委托, 從而便於以任意方式過濾對象,如代碼清單3-19所示,將Filter的擴展方法添加到MyExtension Methods類中。
using System.Collections.Generic; using System; namespace LanguageFeatures { public static class MyExtensionMethods { public static decimal TotalPrices(this IEnumerable<Product> productEnum) { decimal total = 0; foreach (Product prod in productEnum) { total += prod.Price; } return total; } public static IEnumerable<Product> Filter( this IEnumerable<Product> productEnum, Func<Product, bool> selectorParam) { foreach (Product prod in productEnum) { if (selectorParam(prod)) { yield return prod; } } } } }
將Func用作過濾參數,這表示不需要按類型定義委托。該委托接受一個Product參數並返回一個 bool值,判斷條件是:如果此Product包含在結果中,則該值為true,否則為false。
使用上述這種方式實現過濾功能的話,代碼需要做如下修改,如代碼清 單3-20所示,其中顯示了對Default.aspx.cs代碼隱藏類所做的更改。
這里更進了一步,從這種意義上說,現在可以使用在委托中指定的任何標准過濾Product對象,但 必須為所需的每一種過濾定義一個Func,這樣做並不理想。較為簡單的替代方法是使用lambda表達式, 它采用一種簡潔的格式來表達委托中的方法主體。該表達式可以替換委托定義,如代碼清單3-21所示。
代碼清單3-21 用lambda表達式替換委托定義
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Text; using System.Threading.Tasks; namespace LanguageFeatures { public partial class Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } protected string GetMessage() { IEnumerable<Product> products = new ShoppingCart { Products = { new Product {Name = "Kayak", Category = "Watersports", Price = 275M}, new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M}, new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M}, new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M} } }; Func<Product, bool> myFilter = prod => prod.Category == "Soccer"; decimal total = products.Filter(myFilter).TotalPrices(); return String.Format("Soccer Total:{0:c}", total); } } }
lambda表達式以粗體顯示。在表示參數時不需要指定類型,類型將被自動推導出來。=>字符意為 “轉到”,它會將參數鏈接到lambda表達式的結果。在示例中,Product的參數prod鏈接到了一個bool結 果,如果prod的Category參數等於Soccer,則該結果將為true。你可以完全取消Func,使語法更緊湊, 如代碼清單3-22所示。
代碼清單3-22 沒有Func的lambda表達式
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Text; using System.Threading.Tasks; namespace LanguageFeatures { public partial class Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } protected string GetMessage() { IEnumerable<Product> products = new ShoppingCart { Products = { new Product {Name = "Kayak", Category = "Watersports", Price = 275M}, new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M}, new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M}, new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M} } }; decimal total = products.Filter(prod => prod.Category == "Soccer").TotalPrices(); return String.Format("Soccer Total:{0:c}", total); } } }
這個示例中將lambda表達式作為Filter方法的參數。這是展示過濾器的一種簡潔、自然的表達方 式。可以通過擴充lambda表達式的結果部分來組合多個過濾器,如代碼清單3-23所示。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Text; using System.Threading.Tasks; namespace LanguageFeatures { public partial class Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } protected string GetMessage() { IEnumerable<Product> products = new ShoppingCart { Products = { new Product {Name = "Kayak", Category = "Watersports", Price = 275M}, new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M}, new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M}, new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M} } }; decimal total = products.Filter(prod => prod.Category == "Soccer" || prod.Price > 20).TotalPrices(); return String.Format("Soccer Total:{0:c}", total); } } }
這個修訂的lambda表達式將匹配屬於Soccer類別或Price屬性大於20的Product對象。
7.使用自動類型推斷
C# var關鍵字可用於定義本地變量,而無需指定變量類型,如代碼清單3-24所示。這稱為類型推 導或隱式類型。
並不是myVariable沒有類型,這只是要求編譯器從代碼中推導其類型。從隨后的語句可以看出, 編譯器僅允許調用所推導的類的一個成員——本例中為Product。
8.使用匿名類型
結合對象初始化器和類型推導,可以創建簡單的數據存儲對象,而無需定義對應的類或結構。代 碼清單3-25提供了一個示例。
在這個示例中,myAnonType是一個匿名類型對象。這不是說它是動態的,在這種情況下,JavaScript 變量才是動態類型。這只是表明,類型定義將由編譯器自動創建。例如,只能獲取和設置已在初始化 器中定義的屬性。此示例將在瀏覽器中生成以下消息:
Name:Kayak, Type:Watersports
其它有關C#在實際開發中的技巧和特性等到用到的時候再向大家解釋,因為對初學者來說,再往下寫下去會越看越糊塗了。