C# 是一个富有特性的语言,并不是所有的程序员都熟悉本书依赖的所有特性。在本章,我们看看作为一个好的MVC程序员需要知道的C#特性。
1 C#主要特性
1 publicclass Product 2 { 3 publicstring ProductID { get; set; } 4 publicstring Name { get; set; } 5 publicdecimal Price { get; set; } 6 publicstring Catalog { get; set; } 7 }
1.1 使用扩展方法
扩展方法 在你不能拥有,和不能直接修改类时,给类添加方法一个方便的方式。
1 publicclass ShoppingCart 2 { 3 List<Product> Products { get; set; } 4 }
假设我们需要决定ShoppingCart类中Product对象的所有值,但是我们不能修改类本身,也许是因为它来自于第三方,我们没有源代码。幸运地,我们可以使用扩展方法,来得到我们想要的功能。
1 publicstaticclass MyExtensionMethods 2 { 3 publicstaticdecimal TotalPrice(this ShoppingCart cartParam) 4 { 5 decimal total =0; 6 foreach (Product prod in cartParam.Products) 7 { 8 total += prod.Price; 9 } 10 return total; 11 } 12 }
第一个参数前的 this 关键字,使得 TotalPrices 成为一个扩展方法。第一个参数告诉 .net 要为哪个类扩展方法。我们的方法枚举Products,并返回 Product.Price属性的合计。
扩展方法不能使你突破类为它的方法、fields 和属性定义的访问规则。你可以通过扩展方法扩展类的功能,但是只能访问你已经可以访问的类成员。
1 staticvoid Main(string[] args) 2 { 3 ShoppingCart cart =new ShoppingCart 4 { 5 Products =new List<Product> { 6 new Product {Name ="Kayak", Price = 275M}, 7 new Product {Name ="Lifejacket", Price =48.95M}, 8 new Product {Name ="Soccer ball", Price =19.50M}, 9 new Product {Name ="Corner flag", Price =34.95M} 10 } 11 }; 12 13 decimal cartTotal = cart.TotalPrices(); 14 15 Console.WriteLine("Total: {0:c}", cartTotal); 16 Console.ReadKey(); 17 }
1.1.1 将扩展方法应用到一个接口
我们也可以创建扩展方法,将它应用到一个接口。它允许我们在任何安装启用接口的类上调用扩展方法。
1 staticvoid Main(string[] args) 2 { 3 ShoppingCart cart =new ShoppingCart 4 { 5 Products =new List<Product> { 6 new Product {Name ="Kayak", Price = 275M}, 7 new Product {Name ="Lifejacket", Price =48.95M}, 8 new Product {Name ="Soccer ball", Price =19.50M}, 9 new Product {Name ="Corner flag", Price =34.95M} 10 } 11 }; 12 Product[] productArray = { 13 new Product {Name ="Kayak", Price = 275M}, 14 new Product {Name ="Lifejacket", Price =48.95M}, 15 new Product {Name ="Soccer ball", Price =19.50M}, 16 new Product {Name ="Corner flag", Price =34.95M} 17 }; 18 decimal cartTotal = cart.TotalPrices(); 19 decimal arrayTotal = productArray.TotalPrices(); 20 Console.WriteLine("Total: {0:c}", cartTotal); 21 Console.WriteLine("Total: {0:c}", arrayTotal); 22 Console.ReadKey(); 23 } 24 } 25 26 publicstaticclass MyExtensionMethods 27 { 28 publicstaticdecimal TotalPrices(this IEnumerable<Product> productEnum) 29 { 30 decimal total =0; 31 foreach (Product prod in productEnum) 32 { 33 total += prod.Price; 34 } 35 return total; 36 } 37 } 38 39 publicclass ShoppingCart:IEnumerable<Product>40 { 41 public List<Product> Products { get; set; } 42 43 public IEnumerator<Product> GetEnumerator() 44 { 45 return Products.GetEnumerator(); 46 } 47 IEnumerator IEnumerable.GetEnumerator() 48 { 49 return GetEnumerator(); 50 } 51 }
第一个参数的类型已经变为 IEnumerabel<Product>,这意味着方法体中的foreach 循环会直接作用在参数对象上。另外,扩展方法没有变化。切换到接口,意味着我们能计算IEnumerable<Product>枚举的Products的合计值。
1.1.2 创建一个过滤扩展方法
扩展方法可以用来过滤对象的集合。一个操作IEnumerable<T>的扩展方法,会返回一个IEnumerable<T>,可以使用 yield 关键字 来对数据源中的 items 应用选择标准,产生一个缩小的结果集。
1 ShoppingCart cart =new ShoppingCart 2 { 3 Products =new List<Product> { 4 new Product {Name ="Kayak",Catalog="Soccer", Price = 275M}, 5 new Product {Name ="Lifejacket", Price =48.95M}, 6 new Product {Name ="Soccer ball", Price =19.50M}, 7 new Product {Name ="Corner flag", Price =34.95M} 8 } 9 }; 10 11 foreach(Product prod in cart.FilterByCategory("Soccer")) 12 { 13 Console.WriteLine("Name: {0},Price {1:c}", prod.Name,prod.Price); 14 } 15 decimal total = cart.FilterByCategory("Soccer").TotalPrices(); 16 Console.WriteLine(total); 17 Console.ReadKey(); 18 19 publicstatic IEnumerable<Product> FilterByCategory(this IEnumerable<Product> productEnum,string categoryParam) 20 { 21 foreach(Product prod in productEnum) 22 { 23 if(prod.Catalog==categoryParam){ 24 yieldreturn prod; 25 } 26 } 27 }
这个叫做 FilterByCategory的扩展方法,有一个附加的参数,允许我们在调用方法时,注入过滤项。那些 Category 属性 与 参数匹配的 Product 对象,会在结果 IEnumerable<Product>中返回,不匹配的会被丢弃。
1.2 使用Lambda表达式
我们可以使用委托,使得 FilterByCategory 方法更普遍。委托可以以我们选择的任何方式,被每个Product调用,以过滤对象。
1 publicstatic IEnumerable<Product> Filter(this IEnumerable<Product> productEnum, Func<Product, bool> selectorParam) 2 { 3 foreach (Product prod in productEnum) 4 { 5 if (selectorParam(prod)) 6 { 7 yieldreturn prod; 8 } 9 } 10 }
我们使用 Func 作为过滤参数,这意味着我们不用将委托定义为类型。Func 取走一个 Product 参数,并返回 bool。如果 Product包含在结果中,就返回 true。这个参数的另一端有点长。
1 Func<Product, bool> categoryFilter =delegate(Product prod) 2 { 3 return prod.Category =="Watersports"; 4 }; 5 6 IEnumerable<Product> filteredProducts = cart.Filter(categoryFilter); 7 8 foreach (Product prod in filteredProducts) 9 { 10 Console.WriteLine("Name:{0},Price:{1:c}", prod.Name, prod.Price); 11 } 12 13 decimal total = filteredProducts.TotalPrices(); 14 15 Console.WriteLine(total); 16 Console.ReadKey();
我们现在可以在委托中使用指定的标准,过滤 Product 对象。但是我们首先需要为每个标准定义 Func ,这样做不够理想。减少啰嗦的供选择的方式,是使用Lambda表达式,这是一种委托中方法体的简明表达方式。
1 Func<Product, bool> categoryFilter = prod => prod.Category =="Soccer"; 2 IEnumerable<Product> filteredProducts = cart.Filter(categoryFilter); 3
参数不用指定类型的方式被表达出来,它的类型会自动被推论。=> 读作 goes to ,将参数链接到 Lambda 表达式的结果。prod 将 Product 参数 goes to 到一个bool 结果,当 prod 的 Category 参数等于 true 时,返回 true。
我们可以完全地取消 Func ,使得我们的语法更紧凑。
1 IEnumerable<Product> filteredProducts = cart.Filter(prod=>prod.Category=="Soccer");
我们将 Lambda 表达式作为参数提供给 Filter 方法,这是不错且原始的方法,来表达我们想要应用的过滤。
1.2.1 Lambda 的其他格式
1 prod=>prod.Category=="Soccer"||prod.Price<20
我们不需要在Lambda表达式中表达委托的逻辑。我们可以简单地调用方法。如果我们需要一个 委托中有多个参数 的Lambda表达式,我们需要用括号将其包裹起来
1 (prod, count) => prod.Price >20&& count >0
如果表达式过于复杂,我们可以使用大括号
1 (prod, count) => { 2 //...multiple code statements 3 return result; 4 }
1.3 使用匿名类型
var 关键字允许我们创建一个本地变量,而不用明确指定它的类型。结合对象初始化器和类型接口,我们能创建一个简单的 data-storage 对象,而不需要定义相应的类和结构。
1 var myAnonType =new { 2 Name ="MVC", 3 Category ="Pattern"4 }; 5 6 Console.WriteLine("Name: {0}, Type: {1}", myAnonType.Name, myAnonType.Category);
mAnonType 是一个匿名类型对象。它意味着编译器会自动地创建类型定义。强类型依然会实施。你你在它的属性被定义在初始化器后,只能 set 和 get 属性。
C#编译器会根据初始化器中的参数的名字和类型生成类。两个拥有相同属性名字和类型的匿名类型对象,会被指派给相同的自动生成类。这意味着我们能用匿名类型对象创建一个数组。
1.4 Linq
想象偶们有一个 Product 对象集合,我们想要找到价格最高的前三个,并打印他们的名字和价格。一般我们会这样写:
1 Product[] products = { 2 new Product{Name="Kayak",Category="Watersports",Price=275}, 3 new Product{Name="Soccer ball",Category="Soccer",Price=48.95M}, 4 new Product{Name="Soccer ss",Category="Soccer",Price=458.25M}, 5 new Product{Name="Soccer ss",Category="Soccer",Price=448.25M}, 6 new Product{Name="Soccer ss",Category="Soccer",Price=4.25M}, 7 new Product{Name="Soccer ss",Category="Soccer",Price=8.25M}, 8 new Product{Name="Soccer ss",Category="Soccer",Price=48.25M}, 9 new Product{Name="Soccer ss",Category="Soccer",Price=48.5M}, 10 new Product{Name="Soccer ss",Category="Soccer",Price=48.2M}, 11 new Product{Name="Soccer ss",Category="Soccer",Price=418.25M}, 12 new Product{Name="Soccer ss",Category="Soccer",Price=428.25M}, 13 new Product{Name="Soccer ss",Category="Soccer",Price=438.25M} 14 15 }; 16 17 Product[] results =new Product[3]; 18 Array.Sort(products, (item1, item2) =>19 { 20 return Comparer<decimal>.Default.Compare(item2.Price, item1.Price); 21 }); 22 Array.Copy(products, results, 3); 23 foreach (Product p in results) 24 { 25 Console.WriteLine("Item: {0},Cost: {1}", p.Name, p.Price); 26 } 27 Console.ReadKey();
使用 Linq 的话:
1 var results = from product in products 2 orderby product.Price descending 3 select new 4 { 5 product.Name, 6 product.Price 7 }; 8 int count =0; 9 foreach (var p in results) 10 { 11 Console.WriteLine("Item: {0},Cost: {1}", p.Name, p.Price); 12 if (++count ==3) 13 { 14 break; 15 } 16 } 17 Console.ReadKey();
这看上去有一些整齐。使用 select 关键字,返回一个仅包含我们想要的属性的匿名类型。如果还要更强大的,可以使用 Linq 的 点符号。
1 var results = products 2 .OrderByDescending(e => e.Price) 3 .Take(3) 4 .Select(e =>new { e.Name, e.Price }); 5 6 foreach (var p in results) 7 { 8 Console.WriteLine("Item: {0},Cost: {1}", p.Name, p.Price); 9 } 10 Console.ReadKey();
OrderByDescending 方法将数据源中的 item 重新排列。在这个例子中,Lambda表达式返回我们想要用来比较的值。Take 方法返回结果集中,前几个指定数目的。Select 方法允许我们计划我们想要的结果。在这个例子中,我们计划一个匿名对象,包含名字和价格属性。下表中所有的Linq 方法都作用于 IEnumerable<T> 。
扩展方法 |
描述 |
延迟 |
All |
如果所有数据源中的items匹配判断,返回true |
No |
Any |
如果数据源中的Items至少有一个匹配判断,返回True |
No |
Contains |
如果数据源包含一个指定的item或 value,返回 true |
No |
Count |
返回数据源中 items 的个数 |
No |
First |
返回第一个 |
No |
FirstOrDefault |
如果数据源中没有 items,则返回默认值。如果有,返回第一个 |
No |
Last |
返回最后一个 |
No |
LastOrDefault |
如果数据源中没有items,则返回默认值。如果有,则返回最后一个 |
No |
Max Min |
返回Lambda表达式指定项的最大最小值 |
No |
OrderBy OrderByDescending |
基于Lambda表达式返回的值,对数据源排序 |
Yes |
Reverse |
逆转Items 的顺序 |
Yes |
Select |
计划一个查询结果 |
Yes |
SelectMany |
计划每个数据项到一个序列。链接所有的结果序列到一个单独序列 |
Yes |
Single |
返回第一个item。如果有多个匹配,则抛出异常 |
No |
SingleOrDefault |
返回第一个item。如果有多个匹配,则抛出异常。如果没有,则返回默认值。 |
No |
Skip SkipWhile |
跳过指定数目的元素,或当条件匹配时跳过 |
Yes |
Sum |
判定选中的所有values的合计 |
No |
Take TakeWhile |
从数据源开始处选择指定数目的元素。或者选择条件匹配的元素 |
Yes |
ToArray ToDictionary ToList |
将数据源转换为一个数组,或其他集合类型 |
No |
Where |
从数据源中过滤项目,不匹配判断 |
Yes |
1.4.1 延迟
仅包含延期方法的查询,直到 IEnumerable<T> 结果中的 items 可枚举时才会执行。
1 var results = products 2 .OrderByDescending(e => e.Price) 3 .Take(3) 4 .Select(e =>new { e.Name, e.Price }); 5 6 products[2] =new Product { Name ="Stadium", Price = 79500M }; 7 8 foreach (var p in results) 9 { 10 Console.WriteLine("Item: {0},Cost: {1}", p.Name, p.Price); 11 } 12 Console.ReadKey();
我们先定义了查询语句后,修改了Product 数组中的对象。当枚举查询结果时,发现修改生效了。我们看到,查询没有被评估,直到结果被枚举。所以我们做的改变,被反射到输出中。
1.4.2 使用延期前,需三思
1 var results = products 2 .OrderByDescending(e => e.Price) 3 .Take(3) 4 .Select(e =>new { e.Name, e.Price }); 5 6 foreach (var p in results) 7 { 8 Console.WriteLine("Item: {0},Cost: {1}", p.Name, p.Price); 9 } 10 11 products[2] =new Product { Name ="Stadium", Price = 79500M }; 12 13 foreach (var p in results) 14 { 15 Console.WriteLine("Item: {0},Cost: {1}", p.Name, p.Price); 16 } 17 Console.ReadKey();
我们会看到两次枚举的结果不一样。我们并不需要重新定义,更新,或任何其他方式修改Linq 查询。这意味着我们总是依赖延迟查询得到反射到最后更改过的数据源,也意味着查询的结果没有被缓存。如果你想要缓存查询的结果,你应该使用没有延迟的方法,如ToArray,它会立即执行。
1.4.3 Linq 和 IQueryable<t> 接口
Linq不仅能查询居住在内存中的C#对象,而且也能查询XML,非常方便地创建,处理,查询XML content。Parallel LINQ 可能回事下一代 Linq。
Linq 查询也能在从EF获得的数据上执行。EF是 ORM 框架,它允许我们使用 c# 对象处理关系数据,它的机制会在后面看到。
IQueryable<T> 接口由 IEnumerable<T> 派生而来。它用来标识查询执行的结果,针对一个特定的数据源。在我们的例子中,会是Sql Server db。这里没有必要直接使用IQueable<T>。Linq最好的特性之一,就是相同的查询可以被不同类型的数据源执行(objects,XML,db等)。
2 理解 Razor 语法
Razor是MVC3中新 view 引擎的名字。View 引擎处理 web 页面,寻找包含服务端介绍的指定的元素。
Razor 声明以@开头。一个强类型的view,传递一个 model 对象到view。我们能通过 @Model 属性引用方法,字段,属性。
在代码块中包含文本,以HTML 元素开头的,可以这样写
1 @if (Model.Category =="Watersports") { 2 <p>@Model.Category <b>Splash!</b></p>3 }
在代码块中包含文本,不以HTML元素开头的,应该这样写:
1 @if (@Model.Category =="Watersports") { 2 @:Category: @Model.Category <b>Splash!</b>3 }
如果想要添加一些不以 HTML 元素开头的行,可以使用 TEXT 元素,这样做和使用 @: 效果一样。
1 <text>2 Category: @Model.Category <b>Splash!</b>3 <pre>4 Row, row, row your boat, 5 Gently down the stream... 6 </pre>7 </text>
2.1 在代码块中包含多个功能
1 @{
2 if (Model.Category =="Watersports") {
3 @:Category: @Model.Category <b>Splash!</b>4 }
5 if (Model.Price >10) {
6 <h5>Pricey!</h5>7 }
8 }
有两个 if 块,他们都相互独立的操作。这种类型的代码块,主要用来指定变量的值。
2.2 用 View Bag 特性传递数据
1 ViewBag.ProcessingTime = DateTime.Now.ToShortTimeString();
2 @ViewBag.ProcessingTime
ViewBag 是一个动态类型,意味着我们能用指定值的方式,定义他们的属性。使用ViewBag 和使用 ViewData 没有先进之分。
2.3 使用布局
当我们创建视图时,我们指定我们想要的布局,但是我们没有告诉 VS 使用哪一个。在新建View时,对话框告诉我们,如果在 Razor _viewstart文件中设置了此选项,则留空。在View 文件夹中,会看到 _ViewStart.cshtml 文件,该文件中写着 Layout=”~/Views/Shared/_Layout.cshtml”。
View文件以 下划线开头,当用户直接请求该文件时,它不能返回给用户。
当我们打开_Layout.cshtml文件,会看到以下代码:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>@ViewBag.Title</title> 5 <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css"/> 6 <script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")"
7 type="text/javascript"></script> 8 </head> 9 10 <body>11 @RenderBody()
12 </body>13 </html>
这时我们会理解Index.cshtml的这段代码:
1 @{
2 ViewBag.Title="Index";
3 }
它将该页的 title ,传递给了布局页。通过 @ViewBag.Title显示。Razor 使用 @RenderBody() 调用页面 body。
2.4 不使用 Layouts
Razor 布局只是一个选项,如果在创建 view 时没有选择layout选项,你会得到一个 html 模板。
如果不没有 layout,view 必须包含所有必须的 content 。同时必须明确地设定 Layout 为 null。如果没有设置,view 会使用 _ViewStart.cshtml中指定的 layout。