[ASP.NET
MVC 小牛之路]02 - C#知識點提要
本篇博文主要對asp.net mvc開發需要撐握的C#語言知識點進行簡單回顧,尤其是C# 3.0才有的一些C#語言特性。對於正在學asp.net mvc的童鞋,不防花個幾分鐘瀏覽一下。本文要回顧的C#知識點有:特性、自動屬性、對象集合初始化器、擴充方法、Lambda運算式和Linq查詢。C#資深“玩家”可路過。
本文目錄
1.特性(Attributes)
特性(Attributes),MSDN的定義是:通用語言執行平台允許你添加類似關鍵字的描述聲明,叫做attributes, 它對程式中的元素進行標註,如類型、欄位、方法和屬性等。Attributes和Microsoft .NET Framework檔案的中繼資料儲存在一起,可以用來向運行時描述你的代碼,或者在程式啟動並執行時候影響應用程式的行為。
例如,在一個方法前標註[Obsolete]特性,則調用該方法時VS則會提示該方法已到期的警告,如:
又如,在.Net Remoting的遠程對象中,如果要調用或傳遞某個對象,例如類,或者結構,則該類或結構則必須標註[Serializable]特性。還有,我們在構建XML Web服務時用得很多的一個特性就是[WebMegthod],它可讓通過HTTP請求的公開方法的傳回值編碼成XML進行傳遞。
特性實際上就是一個類,[Obsolete]特性的實際類名是ObsoleteAttribute,但我們在標註的時候可以不帶Attribute尾碼,系統在名稱轉換時會自動給我們加上。
上面說的都是些.NET系統定義的一些特性,當然還有很多。瞭解如何自訂特性,有利有我們更好的在ASP.NET MVC編程使用特性,比如給Model類的屬性標註特性來驗證表單輸入的合法性(以後進行介紹)。
下面我們來類比一個ASP.NET MVC經常要用到的StringLenth特性,它用於判斷使用者輸入是否超出長度限制。我們現在來類比它。先定義一個MyStringLenth特性:
// 使用者自訂的帶有可選具名引數的 MyStringLenthAttribute 屬性類別。// 該特性通過AttributeUsage限制它只能用在屬性和欄位上。[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]public sealed class MyStringLenthAttribute : Attribute { public MyStringLenthAttribute(string displayName, int maxLength) { this.MaxLength = maxLength; this.DisplayName = displayName; } //顯示的名稱,對外是唯讀,所以不能通過選擇性參數來賦值,必須在建構函式中對其初始化。 public string DisplayName { get; private set; } //長度最大值,對外是唯讀,所以不能通過選擇性參數來賦值,必須在建構函式中對其初始化。 public int MaxLength { get; private set; } //錯誤資訊,標註時可作為可選具名引數來使用。 public string ErrorMessage { get; set; } //長度最小值,標註時可作為可選具名引數來使用。 public int MinLength { get; set; }}
上面若不加AttributeUsage限制,特性可以聲明在類型(如結構、類、枚舉、委託)和成員(如方法,欄位,事件,屬性,索引)的前面。
然後我們把這個特性應用在下面的Order類之上:
// 應用自訂MyStringLenth特性於Order類的OrderID屬性之上。MinLength和ErrorMessage是具名引數。public class Order { [MyStringLenth("訂單號", 6,MinLength = 3, ErrorMessage = "{0}的長度必須在{1}和{2}之間,請重新輸入!")] public string OrderID { get; set; }}
最後我們看看如何使用MyStringLenth特性驗證使用者輸入字串的長度:
//檢查成員字串長度是否越限。private static bool IsMemberValid(int inputLength, MemberInfo member) { foreach (object attribute in member.GetCustomAttributes(true)) { if (attribute is MyStringLenthAttribute) { MyStringLenthAttribute attr=(MyStringLenthAttribute)attribute; string displayName = attr.DisplayName; int maxLength = attr.MaxLength; int minLength = attr.MinLength; string msg = attr.ErrorMessage; if (inputLength < minLength || inputLength > maxLength) { Console.WriteLine(msg, displayName, minLength, maxLength); return false; } else { return true; } } } return false;}//驗證輸入是否合法private static bool IsValid(Order order) { if (order == null) return false; foreach (PropertyInfo p in typeof(Order).GetProperties()) { if (IsMemberValid(order.OrderID.Length, p)) return true; } return false;}public static void Main() { string input=string.Empty; Order order; do { Console.WriteLine("請輸入訂單號:"); input = Console.ReadLine(); order = new Order { OrderID = input }; } while (!IsValid(order)); Console.WriteLine("訂單號輸入正確,按任意鍵退出!"); Console.ReadKey();}
輸出效果如下:
2.自動屬性
在 C# 3.0 和更高版本中,當屬性的訪問器中不需要其他邏輯時,自動實作屬性可使屬性聲明更加簡潔。
下面樣本示範了屬性的標準實現和自動實現:
class Program { class Person { //標準實現的屬性 int _age; public int Age { get { return _age; } set { if (value < 0 || value > 130) { Console.WriteLine("設定的年齡有誤!"); return; } _age = value; } } //自動實作屬性 public string Name { get; set; } } static void Main(string[] args) { Person p = new Person(); p.Age = 180; p.Name = "小王"; Console.WriteLine("{0}今年{1}歲。",p.Name,p.Age); Console.ReadKey(); }}
自動屬性也可以有不同的存取權限,如:
public string Name { get;private set; }
注意,自動屬性不能定義唯讀或者唯寫的屬性,必須同時提供get和set訪問器:
public string Name { get; }//編譯出錯public string PetName { set; }//編譯出錯
3.對象和集合的初始化器
上面我們示範自動屬性的時候給對象的執行個體初始化時是一個一個屬性進行賦值的,有多少個屬性就需要多少句代碼。C# 3.0和更高版本中有了對象集合初始化器,有了它,只需一句代碼就可初始化一個對象或一個對象集合的所有屬性。這在裡先建立一個“商品”類,用於後面的樣本示範:
/// <summary>/// 商品類/// </summary>public class Product { /// <summary> /// 商品編號 /// </summary> public int ProductID { get; set; } /// <summary> /// 商品名稱 /// </summary> public string Name { get; set; } /// <summary> /// 商品描述 /// </summary> public string Description { get; set; } /// <summary> /// 商品價格 /// </summary> public decimal Price { get; set; } /// <summary> /// 商品分類 /// </summary> public string Category { set; get; }}
基於上面定義好的商品類,下面代碼示範了如何通過初始化器來建立商品類的執行個體對象和集合:
static void Main(string[] args) { //對象初始化器的使用 (可只給部分欄位賦值) Product product = new Product { ProductID = 1234, Name = "西瓜", Price = 2.3M };//建立並初始化一個執行個體 //集合初始化器的使用 List<Product> proList = new List<Product> { new Product { ProductID = 1234, Name = "西瓜", Price = 2.3M }, new Product { ProductID = 2345, Name = "蘋果", Price = 5.9M }, new Product { ProductID = 3456, Name = "櫻桃", Price = 4.6M } }; //列印 Console.WriteLine("對象初始化器:{0} {1} {2}", product.ProductID, product.Name, product.Price); foreach (Product p in proList) { Console.WriteLine("集合初始化器:{0} {1} {2}", p.ProductID, p.Name, p.Price); } Console.ReadKey();}
另外還有一些其它類型也可以使用初始化器,如下:
/數組使用初始化器string[] fruitArray = {"apple","orange","plum" };//匿名型別使用初始化器var books = new { Title = "ASP.NET MVC 入門", Author = "小王", Price = 20 };//字典類型使用初始化器Dictionary<string, int> fruitDic = new Dictionary<string, int>() { { "apple", 10 }, { "orange", 20 }, { "plum", 30 }};
4.擴充方法
擴充方法使您能夠向現有類型“添加”方法,而無需建立新的衍生類別型或修改原始類型。擴充方法是一種特殊的靜態方法,但可以像擴充類型上的執行個體方法一樣進行調用。例如,我們可以讓Random類的所有執行個體對象擁有一個返回隨機bool值的方法。我們不能對Random類本身進行修改,但可以對它進行擴充,如下代碼所示:
static class Program { /// <summary> /// 隨機返回 true 或 false /// </summary> /// <param name="random">this參數自動指定到Random的執行個體</param> /// <returns></returns> public static bool NextBool(this Random random) { return random.NextDouble() > 0.5; } static void Main(string[] args) { //調用擴充方法 Random rd = new Random(); bool bl = rd.NextBool(); Console.WriteLine(bl.ToString()); Console.ReadKey(); }}
注意,擴充方法必須在非泛型的靜態類中定義,上面的Program類如不加static修飾符則會報錯。
我們可以建立一個介面的擴充方法,這樣實現該介面的類都可以調用該擴充方法。看下面一個完整樣本:
/// <summary>/// 購物車類 (實現 IEnumerable<Product> 介面)/// </summary>public class ShoppingCart : IEnumerable<Product> { public List<Product> Products { get; set; } public IEnumerator<Product> GetEnumerator() { return Products.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }}/// <summary>/// 定義一個靜態類,用於實現擴充方法(注意:擴充方法必須定義在靜態類中)/// </summary>public static class MyExtensionMethods { /// <summary> /// 計算商品總價錢 /// </summary> public static decimal TotalPrices(this IEnumerable<Product> productEnum) { decimal total = 0; foreach (Product prod in productEnum) { total += prod.Price; } return total; }}class Program { static void Main(string[] args) { // 建立並初始化ShoppingCart執行個體,注入IEnumerable<Product> IEnumerable<Product> products = new ShoppingCart { Products = new List<Product> { new Product {Name = "Kayak", Price = 275}, new Product {Name = "Lifejacket", Price = 48.95M}, new Product {Name = "Soccer ball", Price = 19.50M}, new Product {Name = "Corner flag", Price = 34.95M} } }; // 建立並初始化一個普通的Product數組 Product[] productArray = { new Product {Name = "Kayak", Price = 275M}, new Product {Name = "Lifejacket", Price = 48.95M}, new Product {Name = "Soccer ball", Price = 19.50M}, new Product {Name = "Corner flag", Price = 34.95M} }; // 取得商品總價錢:用介面的方式調用TotalPrices擴充方法。 decimal cartTotal = products.TotalPrices(); // 取得商品總價錢:用普通數組的方式調用TotalPrices擴充方法。 decimal arrayTotal = productArray.TotalPrices(); Console.WriteLine("Cart Total: {0:c}", cartTotal); Console.WriteLine("Array Total: {0:c}", arrayTotal); Console.ReadKey(); }}
執行後輸出如下結果:
5.Lambda 運算式
Lambda 運算式和匿名函數其實是一件事情。不同是,他們文法表現形式不同,Lambda 運算式在文法上實際上就是匿名函數的簡寫。直接介紹匿名函數和Lambda運算式的用法沒什麼意思,在這裡,我要根據實際應用來講一個兩者用法的例子,這樣在介紹知識點的同時也能和大家分享一下解決問題的思想。
假如我們要實現一個功能強大的商品查詢方法,這個商品查詢方法如何查詢商品是可以由使用者自己來決定的,使用者可以根據價格來查詢商品,也可以根據分類來查詢商品等等,也就是說使用者可以把自己的查詢邏輯傳遞給這個查詢方法。要編寫這樣一個方法,我們很自然的會想到用一個委託來作為這個方法的參數,這個委託就是使用者處理商品查詢的邏輯。 我們不防把這個查詢方法稱為“商品查詢器”。我們可以用靜態擴充方法來實現這個“商品查詢器“,這樣每個商品集合對象(如 IEnumerable<Product> products)可以直接調用該靜態方法返回查詢結果。解決問題的思想有了,接下來就是實現了。或許你對這一段描述有點蒙,結合代碼可能讓你更清晰。下面是這個“商品查詢器”-Filter方法的實現代碼:
/// <summary>/// 定義一個靜態類,用於實現擴充方法/// </summary>public static class MyExtensionMethods { /// <summary> /// 商品查詢器 /// </summary> /// <param name="productEnum">擴充類型的執行個體引用</param> /// <param name="selectorParam">一個參數類型為Product,傳回值為bool的委託</param> /// <returns>查詢結果</returns> public static IEnumerable<Product> Filter(this IEnumerable<Product> productEnum, Func<Product, bool> selectorParam) { foreach (Product prod in productEnum) { if (selectorParam(prod)) { yield return prod; } } }}
沒錯,我們就是用這麼簡短的Filter方法來滿足各種需求的查詢。上面Product類使用的是前文定義的。這裡也再一次見證了擴充方法的功效。為了示範Filter查詢方法的調用,我們先來造一批資料:
static void Main(string[] args) { // 建立商品集合 IEnumerable<Product> products = new ShoppingCart { Products = new List<Product> { new Product {Name = "西瓜", Category = "水果", Price = 2.3M}, new Product {Name = "蘋果", Category = "水果", Price = 4.9M}, new Product {Name = "ASP.NET MCV 入門", Category = "書籍", Price = 19.5M}, new Product {Name = "ASP.NET MCV 提高", Category = "書籍", Price = 34.9M} } };}
接下來我們繼續在上面Main方法中來調用查詢方法Filter:
//用匿名函數定義一個具體的查詢需求Func<Product, bool> fruitFilter = delegate(Product prod) { return prod.Category == "水果";};//調用Filter,查詢分類為“水果”的商品IEnumerable<Product> filteredProducts = products.Filter(fruitFilter);//列印結果foreach (Product prod in filteredProducts) { Console.WriteLine("商品名稱: {0}, 單價: {1:c}", prod.Name, prod.Price);} Console.ReadKey();
輸出結果為:
上面我們使用的是委託和匿名函數來處理使用者查詢邏輯,並把它傳遞給Filter方法,滿足了前面所說的需求。但若使用Lambda運算式代替上面的匿名函數能使上面的代碼看上去更簡潔更人性化,如下代碼所示:
Func<Product, bool> fruitFilter = prod => prod.Category == "水果";IEnumerable<Product> filteredProducts = products.Filter(fruitFilter);
沒有了delegate關鍵字,沒有了大小括弧,看上去更舒服。當然上面兩行代碼可以繼續簡化為一行:
IEnumerable<Product> filteredProducts = products.Filter(prod => prod.Category == "水果");
這三種方式輸出結果都是一樣的。然後,我們還可以通過Lambda運算式實現各種需求的查詢:
//查詢分類為“水果”或者單價大於30元的商品IEnumerable<Product> filteredProducts = products.Filter(prod => prod.Category == "水果" || prod.Price > 30);
通過這個樣本,相信大家已經清晰的瞭解並撐握了Lambda運算式的簡單應用,而這就足夠了:)。
6.LINQ
最後簡單回顧一下LINQ。LINQ(Language Integrated QueryLanguage-integrated Query (LINQ))是 VS 2008 和 .NET Framework 3.5 版中一項突破性的創新,它在對象領域和資料領域之間架起了一座橋樑。
上面講Lambda運算式時,用到的查詢結果集的方式未免還是有點麻煩(因為自訂了一個Filter擴充方法),而Linq本身就集合了很多擴充方法,我們可以直接使用,大大的簡化了編寫查詢代碼的工作。例如,對於這樣一個資料集合:
Product[] products = { new Product {Name = "西瓜", Category = "水果", Price = 2.3M}, new Product {Name = "蘋果", Category = "水果", Price = 4.9M}, new Product {Name = "空心菜", Category = "蔬菜", Price = 2.2M}, new Product {Name = "地瓜", Category = "蔬菜", Price = 1.9M} };
如果要查詢得到價錢最高的三個商品資訊,如果不使用Linq,我們可能會先寫一個排序方法,對products根據價錢由高到低進行排序,排序時需要建立一個新的Product[]對象用於儲存排序好的資料。但用Linq可大大減少工作量,一兩句代碼就能搞定。如下代碼所示,查出價錢最高的三個商品:
var results = from product in products orderby product.Price descending select new { product.Name, product.Price };//列印價錢最高的三個商品int count = 0;foreach (var p in results) { Console.WriteLine("商品:{0},價錢:{1}", p.Name, p.Price); if (++count == 3) break;}Console.ReadKey();
輸出結果:
能熟練使用Linq是一件很爽的事情。上面的Linq語句和我們熟悉的SQL查詢語句類似,看上去非常整潔且易懂。但並不是每一種SQL查詢語句在C#都有對應的關鍵字,有時候我們需要使用另外一種Linq查詢方式,即“點號”方式的Linq查詢方式,這種方式中的Linq查詢方法都是擴充方法。如下面這段代碼和上面實現的效果是一樣的:
var results = products .OrderByDescending(e => e.Price) .Take(3) .Select(e => new { e.Name,e.Price});foreach (var p in results) { Console.WriteLine("商品:{0},價錢:{1}", p.Name, p.Price);}Console.ReadKey();
雖然類SQL的Linq查詢方式比這種方式看上去更一目瞭然,但並不是每一種SQL查詢語句在C#都有對應的關鍵字,比如這裡的Take擴充方法就是類SQL的Linq查詢文法沒有的功能。
注意,有些Linq擴充方法分為“延後查詢”(deferred)和“即時查詢”(immediate)。延後查詢意思是擁有“延後查詢”擴充方法的Linq語句只有當調用結果集對象的時候才開始真正執行查詢,即時查詢則是立即得到結果。比如上面的Linq語句的OrderByDescending擴充方法就是一個“延後查詢”方法,當程式執行到Linq語句定義部分時並沒有查詢出結果並放到results對象中,而是當程式執行到foreach迴圈時才真正執行Linq查詢語句得到查詢結果。我們可以做個測試,在Ling語句之後,我們再將products[1]對象重新賦值,如下代碼所示:
var results = products .OrderByDescending(e => e.Price) .Take(3) .Select(e => new { e.Name, e.Price });//在Linq語句之後對products[1]重新賦值products[1] = new Product { Name = "榴蓮", Category = "水果", Price = 22.6M };//列印foreach (var p in results) { Console.WriteLine("商品:{0},價錢:{1}", p.Name, p.Price);}Console.ReadKey();
輸出結果為:
我們發現results是重新賦值之後的結果。可想而知,查詢語句是在results被調用之後才真正執行的。
Linq非常強大也非常好用,這裡只是把它當作一個學習ASP.NET MVC之前需掌握的知識點進行簡單回顧。要靈活熟練地使用Linq還需要經常使用才行。
以上就是[ASP.NET MVC 小牛之路]02 - C#知識點提要的內容,更多相關內容請關注topic.alibabacloud.com(www.php.cn)!