Pro ASP.NET MVC –第四章 语言特性精华
C#语言有很多特性,并不是所有的程序员都了解本书我们将会使用的C#语言特性。因此,在本章,我们将了解一下作为一个好的MVC程序员需要了解C#语言的特性。
每个特性我们都只是简要介绍。如果你想深入了解LINQ或C#,你可以去参考Adam的三本书:Introducing Visual C#,你可以全面地了解C#语言;Pro LINQ in C#,你深入了解LINQ;Pro .NET Parallel Programming in C#,你可以详细地了解.NET所支持的异步编程。这些书都出自Apress出版社
1 创建示例项目
为了更好的演示C#语言特性,我们在本章创建一个名为LanguageFeatures的ASP.NET MVC 4 WEB APPLICATION.
首先,我们需要一个简单的控制器,以演示这些语言特性,因为我们在Controllers文件夹下添加HomeController.cs文件,然后添加如下的代码
publicclassHomeController : Controller { publicstring Index() { return"Navigate to a URL to show an example"; } } |
后续,我们将为每个示例都创建一个行为方法,因此,为了简化工程,Index行为之返回最基本的消息。为了显示行为方法的结果,我们在Views文件夹的Home文件夹下添加一个名为Result.cshtml的视图文件,它的内容如下:
@model String @{Layout = null} <!DOCTYPEhtml> <html> <head> <metaname="viewport"content="width=device-width"/> <title>Result</title> </head> <body> <div> @Model </div> </body> </html> |
你会发现,该视图是一个强类型视图,因为model的类型是字符串——它不是一个复杂的类型,因为我们可以轻易地把结果通过字符串呈现出来。
2使用自动实现属性
C#类通过属性对外暴露数据,从而使得设置数据和获取数据解耦。下面的Product类演示了C#类的属性特性
publicclassProduct { privatestring name; publicstring Name { get { return name; } set { name = value; } } } |
Product类包含了一个属性Name。读取Name属性值的时候,get代码语句将被执行;当为Name属性设置一个值使,set代码语句将执行。下面的代码演示了如何使用属性
publicViewResult AutoProperty() { Product myProduct = newProduct(); myProduct.Name = "Kayak"; string productname = myProduct.Name; return View("Result", (object)String.Format("Product name : {0}", productname)); } |
你可以发现,属性如同一个常规字段一样读取和设置值。推荐使用属性而不是字段,这是因为你可以在set或get的语句做更改,而不需更改其他依赖该属性的类。
到目前为止,一切看起来都很好。但是如果一个类有许多属性,每个属性都即是读取一个字段。那么我们可以使用自动属性的方式来简化属性的set和get语句
publicint ProductID { get; set; } publicstring Description { get; set; publicdecimal Price { get; set; } publicstring Category { get; set; } |
请注意,在使用自动属性时,有下面几点值得注意:首先不需要为set和get添加任何代码;其次我们并没有定义属性对应的字段。当C#编译器编译我们创建的类的时候,会自动为我们完成这两项任务。此外,使用自动属性和常规的属性没有任何差别。
3使用对象和集合初始化器
在工作中,一个常见的烦人的任务就是创建一个新对象并对其的属性赋值。下面的例子展示常规的方式
publicViewResult CreateProduct() { Product myProduct = newProduct(); myProduct.ProductID = 100; myProduct.Name = "Kayak"; myProduct.Description = "A boat for one person"; myProduct.Price = 275m; myProduct.Category = "Watersports"; return View("Result", (object)String.Format("Category : {0}", myProduct.Category)); } |
我们必须通过三个步骤才能完成创建一个Product对象并输出结果:创建对象,设置参数值,最后调用View方法把结果显示在视图中。幸运地是,我们可以使用对象初始化器特性,该特性允许我们创建对象的同时设置属性的值
publicViewResult CreateProduct() { Product myProduct = newProduct { ProductID=100, Name="Kayak", Description="A boat for one person", Price=275m, Category="Watersports" }; return View("Result", (object)String.Format("Category : {0}", myProduct.Category)); } |
上面代码中new Production后的{}就是对象初始化器,通过初始化器我们为参数提供值从而完成构造过程。这个特性还使用于集合和数组的初始化。比如
publicViewResult CreateCollection() { string[] stringArray = { "apple", "orange", "plum" }; List<int> intList = newList<int> { 10, 20, 30, 40 }; Dictionary<string, int> myDict = newDictionary<string, int>{ {"apple", 10}, {"orange", 20}, {"plum", 30} }; return View("Result", (object)stringArray[1]); } |
4使用扩展方法
通过扩展方法可以方便地向不属于你或你不能修改的类添加方法。下面的代码展示了ShoppingCart类,它位于Models文件夹下,并代表一组Product对象集合
publicclassShppingCart { publicList<Product> Products { get; set; } } |
该类非常简单,仅仅是包装了Products对象的集合。现在假设我们要计算ShoppingCart类中Product对象的总价,而我们又不能修改ShoppingCart类,因为有可能ShoppingCart类由第三方提供或者我们没有ShoppingCart的源代码。在这种情况下,我们就可以使用扩展方法来完成计算Product总价这个需求。
publicstaticclassMyExtensionMethods { publicstaticdecimal TotalPrices(this ShppingCart cartParam) { return cartParam.Products.Sum(p => p.Price); } } |
TotalPrinces的参数中的一个参数this是一个关键字。该参数告诉.NET将把扩展方法应用于哪一个类,在我们的列子中,指ShoppingCart。我们可以引用ShoppingCart的实例,从而使扩展方法可以应用到实例cartParam上。下面的代码演示了如何使用我们创建的扩展方法
publicViewResult UseExtension() { ShppingCart cart = newShppingCart { Products = newList<Product> { newProduct{Name="Kayak", Price=275m}, newProduct{Name="Lifejacket", Price=48.95m}, newProduct{Name="Soccer ball", Price=19.50m}, newProduct{Name="Corner flag", Price=34.95m} } }; decimal cartTotal = cart.TotalPrices(); return View("Result", (object)string.Format("Total:{0:c}", cartTotal)); } |
运行结果
扩展接口
我们同样还可以对接口应用扩展方法,它允许我们在所有实现该接口的类中调用扩展的方法。下面我们更ShoppingCart类使其实现IEnumerable<Product>接口
publicclassShppingCart : IEnumerable<Product> { publicList<Product> Products { get; set; } publicIEnumerator<Product> GetEnumerator() { return Products.GetEnumerator(); } IEnumeratorIEnumerable.GetEnumerator() { return GetEnumerator(); } } |
接着,我们更新扩展方法
publicstaticdecimal TotalPrices(thisIEnumerable<Product> productParam) { return productParam.Sum(p => p.Price); } |
现在扩展方法TotalPrice的的一个参数this指向IEnumerable<Product>,这就意味着求和方法直接与Product工作。扩展方法转到接口意味着我们可以计算任何IEnumerable<Product>中Product的总和,这个IEnumerable<Product>即可以是ShoppingCart中的一个实例,也可以是一个Products数组。
publicViewResult UseExtensionEnumerable() { IEnumerable<Product> products = newShppingCart { Products = newList<Product> { newProduct{Name="Kayak", Price=275m}, newProduct{Name="Lifejacket", Price=48.95m}, newProduct{Name="Soccer ball", Price=19.50m}, newProduct{Name="Corner flag", Price=34.95m} } }; Product[] productArray = { newProduct{Name="Kayak", Price=275m}, newProduct{Name="Lifejacket", Price=48.95m}, newProduct{Name="Soccer ball", Price=19.50m}, newProduct{Name="Corner flag", Price=34.95m} }; decimal cartTotal = products.TotalPrices(); decimal arrayTotal = productArray.TotalPrices(); return View("Result", (object)string.Format("Cart total:{0:c}, Array total: {1:c}", cartTotal, arrayTotal)); } |
结果如下:
创建过滤扩展方法
关于扩展方法,最后还有一点需要强调,那就是扩展方法可以用于过滤对象集合。一个扩展方法操作IEnumerable<T>,并且返回一个IEnumerable<T>,那么可使用yield关键字在源子项上应用选择条件,从而生成一组过滤后后的结果。
publicstaticIEnumerable<Product> FilterByCategory(thisIEnumerable<Product> products, string category) { foreach (Product product in products) { if (product.Category == category) yieldreturn product; } } |
很简单,不用解释,下面我们来使用该方法
publicViewResult UseFilterExtensionsMethod() { IEnumerable<Product> products = newShppingCart { Products = newList<Product> { newProduct{Name="Kayak", Category="Watersports", Price=275m}, newProduct{Name="Lifejacket", Category="Watersports", Price=48.95m}, newProduct{Name="Soccer ball", Category="Soccer", Price=19.50m}, newProduct{Name="Corner flag", Category="Soccer", Price=34.95m} } }; decimal total = 0; foreach (Product product in products.FilterByCategory("Soccer")) { total += product.Price; } return View("Result", (object)string.Format("Total:{0:c}", total)); } |
运行结果:
5使用Lamdba表达式
如果使用代理(delegate),那么可以使FilterByCategory更通用。
publicstaticIEnumerable<Product> FilterByCategory(thisIEnumerable<Product> products, Func<Product, bool> sector) { foreach (Product product in products) { if (sector(product)) yieldreturn product; } } |
那我们如何调用呢?
publicViewResult UseFilterExtensionsMethod() { IEnumerable<Product> products = newShppingCart { Products = newList<Product> { newProduct{Name="Kayak", Category="Watersports", Price=275m}, newProduct{Name="Lifejacket", Category="Watersports", Price=48.95m}, newProduct{Name="Soccer ball", Category="Soccer", Price=19.50m}, newProduct{Name="Corner flag", Category="Soccer", Price=34.95m} } }; decimal total = 0; Func<Product, bool> categoryFilter = delegate(Product product) { return product.Category == "Soccer"; }; foreach (Product product in products.FilterByCategory(categoryFilter)) { total += product.Price; } return View("Result", (object)string.Format("Total:{0:c}", total)); } |
现在我们可以在delegate中指定任何条件,从而过滤Product对象。但是我们必须定义为每个条件定义一个Func,然而通过delegate这种方式这不是完美的。那么解决由于使用delegate而产生冗长代码的解决方法是使用lamdba表达式,它是delegate方法的精简模式。比如上面的Func可以使用下面的lamdba表达式来定义
Func<Product, bool> categoryFilter = p => { return p.Category == "Soccer"; }; |
请注意,上面Lampda表达式中的参数p并没有指定类型,p的类型会被自动推断。=>符号读作"Goes to",而且把参数p和lamdbp表达式的结果联系起来。在我们的例子中,一个名为p的Product参数,产生一个bool结果,如果参数的Category等于Soccer,那么返回真。接下来,我们取消Func的定义,把lamdba表达式作为参数直接放到FilterByCategory方法中
foreach (Product product in products.FilterByCategory(p=>p.Category=="Soccer")) { total += product.Price; } |
这么做,从而就实现了不需要为每个搜索专门定义一个Func,这就非常好并且以自然的方式表达了我们需应用的过滤条件。我们还可以通过扩展lamdba表达式结果部分从而合并多个过滤条件
foreach (Product product in products.FilterByCategory(p=>p.Category=="Soccer" || p.Price > 20)) { total += product.Price; } |
使用Lamdbo表达式,需要注意:
- 不要在Lamdba表达式中呈现逻辑,而是直接调用一个方法,比如prod => EvaluateProduct(production)
- 如果需要多个参数,那么可以这么使用 (prod, count) => prod.Price > 20 && count > 0
- 如果需要在Lamdba中呈现逻辑,那么可以使用{}。比如(prod, count) => {//… return result;}
6使用自动类型
C#中的var关键字可以用于定义一个变量而不用知道变量的具体类型。比如
var myVariable = newProduct { Name = "Kayak", Category = "Watersports", Price = 275m }; string name = myVariable.Name; // 合法 int count = myVariable.Count; // 编译错误 |
上述的代码中,并不是说myVariable没有一个类型,而是我们要求编译器根据代码推断其类型。从代码中可以看出,编译器将使用Product作为myVariable变量的类型。
7使用匿名类型
通过对象初始化器和类型推断,我们可以创建一个简单的数据存储对象,而不需要定义对应的类或结构,比如
var myAnonymousType = new { Name = "MVC", Category = "Pattern" }; |
在这个例子中,MyAnonymousType是一个匿名类型对象。这和动态语言JavaScript中的动态类型不一样。匿名类型是指类型定义将由编译器自动创建。强类型仍然是必须的。你仅仅可以对通过初始化器定义的属性进行get和set操作。
C#编译器通过初始化器中参数的名字和类型来生产类。如果两个匿名类型有相同的属性名和类型,那么将自动的生产为同一个类。这就意味这我们可以创建匿名类型对象数组:
publicViewResult CreateAnonymousArray() { var oddsAndEnds = new[] { new {Name = "MVC", Category="Pattern"}, new {Name = "Hat", Category="Clothing"}, new {Name = "Apple", Category="Fruit"} }; StringBuilder sb = newStringBuilder(); foreach (var item in oddsAndEnds) { sb.Append(string.Format("{0} ", item.Name)); } return View("Result", (object)sb.ToString()); } |
请注意,我们使用var声明变量数组。我们必须这么做,是因为我们并没有与平常一样创建一个常见类型的数组,而是创建了一个没有特定类型的数组。尽管我们没有为这些对象定义一个类,但我们仍旧可以枚举数组的内容并读取Name的值。这非常游泳,加入没有这个特性,我们就根本不能创建匿名类型数组。或者,即便我们创建了数组,我们也不能对该数组做任何事。下图是上面代码的运行结果:
8执行LINQ
到目前为止我们使用的特性,如果使用LINQ也都可以很好地完成。我们也非常喜欢LINQ。如果你之前从未使用过LINQ,那么你已经落伍啦。LINQ类似于SQL语法,它用于查询类中的数据。试想一下假如入我们拥有一个Products对象集合,并且我们希望发现价格最高的三个,然后把结果传递给View方法。如果不使用LINQ,那么我们需要这么做:
publicViewResult FindProducts() { Product[] products = { newProduct {Name = "Kayak", Category = "Watersports", Price = 275M}, newProduct {Name = "Lifejacket", Category = "Watersports", Price = 48.95M}, newProduct {Name = "Soccer ball", Category = "Soccer", Price = 19.50M}, newProduct {Name = "Corner flag", Category = "Soccer", Price = 34.95M} }; Product[] foundProducts = newProduct[3]; // 排序数组 Array.Sort(products, (item1, item2) => { returnComparer<decimal>.Default.Compare(item1.Price, item2.Price); }); // 把价格最高的三个输入到结果中 Array.Copy(products, foundProducts, 3); // 输出结果 StringBuilder sb = newStringBuilder(); foreach (Product product in foundProducts) { sb.AppendFormat("Price: {0:c}", product.Price); } return View("Result", (object)sb.ToString()); } |
那如果我们使用LINQ方法呢?
publicViewResult FindProducts() { Product[] products = { newProduct {Name = "Kayak", Category = "Watersports", Price = 275M}, newProduct {Name = "Lifejacket", Category = "Watersports", Price = 48.95M}, newProduct {Name = "Soccer ball", Category = "Soccer", Price = 19.50M}, newProduct {Name = "Corner flag", Category = "Soccer", Price = 34.95M} }; var foundProducts = from product in products orderby product.Price descending selectnew { product.Name, product.Price }; // 输出结果 int count = 0; StringBuilder sb = newStringBuilder(); foreach (Product product in foundProducts) { sb.AppendFormat("Price: {0:c}", product.Price); if (++count == 3) break; } return View("Result", (object)sb.ToString()); } |
高亮的部分就是LINQ查询,你可以发现它和SQL语法很相似。我们首先采用价格降序的方式排序Product对象,然后使用select关键字返回匿名类型,该类型仅仅包含Name和Price属性。LINQ的这种样式被称为查询语法,正式这种方式使得开发人员很快就能上手LINQ。该查询的巧妙之处就在于它返回的每一个匿名类型对象来自于原始查询的Product对象(一个匿名对象对应一个Product对象),然后我们调用这个结果集,最后输出前三个匿名类型对象的价格信息。
那么如果,我们只返回三个匿名对象,我们需要这样来实现
publicViewResult FindProducts() { Product[] products = { newProduct {Name = "Kayak", Category = "Watersports", Price = 275M}, newProduct {Name = "Lifejacket", Category = "Watersports", Price = 48.95M}, newProduct {Name = "Soccer ball", Category = "Soccer", Price = 19.50M}, newProduct {Name = "Corner flag", Category = "Soccer", Price = 34.95M} }; //-------Version 3--------- var foundProducts = products.OrderByDescending(p => p.Price). Take(3).Select(p => new { p.Name, p.Price }); // 输出结果 StringBuilder sb = newStringBuilder(); foreach (var product in foundProducts) { sb.AppendFormat("Price: {0:c}", product.Price); } return View("Result", (object)sb.ToString()); } |
首先,我们要接受上面高亮部分的代码为LINQ查询。它看起来没有查询语法那么友好,但并不是所有的LINQ特性都会有C#关键字。对于一个严谨的LINQ程序员,我们需要从LINQ查询到使用扩展方法。每个LINQ扩展方法都是应用于IEnumerable<T>,并且LINQ的返回结果的类型也是IEnumerbale<T>,这就使我们可以为了完成复杂的查询从而进行链式的LINQ查询。
OrderByDescending方法重新排序数据源。在本例中,lamdba表达式返回我们用于比较的值。Take方法从结果集中返回指定数目的前几个子项。Select方法允许我们构建最终的返回结果。在本例中,我们创建了一个匿名类型对象,该匿名类型对象包含Name和Price属性。请注意,我们也不需要执行匿名类型对象中属性的类型,因为C#在Select方法中可以自动推断匿名类型属性的类型。
下标列出了一些常见的LINQ扩展方法,请注意下表中所有的方法都对IEnumerable<T>的操作
扩展方法 |
描述 |
是否可递延 |
All |
是否所有的项目都符合条件 |
否 |
Any |
是否有任意一个项目符合条件 |
否 |
Contains |
是否包含特定的项目或者某个值 |
否 |
Count |
返回数据源项目的个数 |
否 |
First |
返回数据源项目中的第一个 |
否 |
FirstorDefault |
返回数据源项目中的第一个,或如果没有符合的则返回默认值 |
否 |
Last |
返回数据源项目的最后一个 |
否 |
LastOrDefault |
返回数据源项目的最后一个,或如果没有符合的则返回默认值 |
否 |
Max |
根据lamdba表达式返回最大或最小的值 |
否 |
OrderBy |
根据执行的条件对数据源中的项目排序 |
是 |
Reverse |
倒置数据源中的项目的顺序 |
是 |
Select |
从一个查询中创建返回的结果 |
是 |
Single |
返回数据源中的第一个项目,当多个项目满足条件时抛出异常 |
否 |
SingleOrDefault |
返回数据源中的的一个项目,或如何没有符合的则返回默认值,或当多个项目满足条件时抛出异常 |
否 |
Skip |
跳过执行数目的项目,或者一直跳到满足条件之后的项目 |
是 |
Sum |
根据条件对指定的值求和 |
否 |
Take |
从数据源的一个项目开始选择指定数目的子项,或者根据条件从的一个开始选择 |
是 |
ToArray |
转化数据源为数组,或其他集合类型 |
否 |
Where |
从数据源中根据条件过滤项目 |
是 |
理解递延的LINQ查询
你应该已经注意到上表中最后一列是否可递延。在一个LINQ的扩展方法执行后会有一个非常有趣的变化。如果一个查询只包含递延方法,那么直到结果集中的项目被枚举时,递延扩展方法才会被执行。下面的代码证实了这点
publicViewResult FindProducts() { Product[] products = { newProduct {Name = "Kayak", Category = "Watersports", Price = 275M}, newProduct {Name = "Lifejacket", Category = "Watersports", Price = 48.95M}, newProduct {Name = "Soccer ball", Category = "Soccer", Price = 19.50M}, newProduct {Name = "Corner flag", Category = "Soccer", Price = 34.95M} }; //-------Version 3--------- var foundProducts = products.OrderByDescending(p => p.Price). Take(3).Select(p => new { p.Name, p.Price }); products[2] = newProduct { Name = "Stadium", Price = 79600M }; // 输出结果 StringBuilder sb = newStringBuilder(); foreach (var product in foundProducts) { sb.AppendFormat("Price: {0:c}", product.Price); } return View("Result", (object)sb.ToString()); } |
在定义LINQ查询和执行LINQ查询结果的枚举之前,我们更改了数据源Products中的一个项目,那么输出结果将为:
而如果我们使用非递延扩展方法,那么会产生什么结果呢?
publicViewResult SumProducts() { Product[] products = { newProduct {Name = "Kayak", Category = "Watersports", Price = 275M}, newProduct {Name = "Lifejacket", Category = "Watersports", Price = 48.95M}, newProduct {Name = "Soccer ball", Category = "Soccer", Price = 19.50M}, newProduct {Name = "Corner flag", Category = "Soccer", Price = 34.95M} }; var total = products.Sum(p => p.Price); products[2] = newProduct { Name = "Stadium", Price = 79600M }; return View("Result", (object)total.ToString()); } |
那么结果为
你会发现,Stadimn项目,并没有包含在求和的结果中。这是因为Sum方法被调用时就立即执行了,而递延方法只有在结果集被使用时才会被执行。
9使用异步方法
.NET 4.5改进了异步方法的处理。异步方法一旦执行,就会在后台工作,当它结束后发出通知,这就使你的代码更关注业务,而不用担心后台执行的任务。异步方法是非常重要的哦工具,它可以移除代码瓶颈,并允许程序可以利用多线程和多核并行的执行工作。
C#和.NET对异步方法近乎达到了完美的支持,但是代码还是有点冗长,不习惯并行编程开发人员经常深陷这些不常见的语法。举一个简单的例子,下面的异步方法GetPageLength,该方法定义在Models文件夹下的MyAsyncMethods文件中
publicstaticTask<long?> GetPageLength() { HttpClient client = newHttpClient(); var httpTask = client.GetAsync("http://appress.com"); // 在HTTP请求完成之前,我们可以做其他的事情 // ... return httpTask.ContinueWith((Task<HttpResponseMessage> antecendent) => { return antecendent.Result.Content.Headers.ContentLength; }); } |
这是一个简单的方法,它使用System.Net.Http.HttpClient对象请求Appress主页,并返回其内容的长度。我们高亮了可能引起混淆的代码,它就是task continuation的列子。
.NET把通过异步完成的工作定义为Task。Task对象是基于后台工作结果的强类型对象。因此,当我们调用HttpClient.GetAsync方法时,我们将得到的结果是Task<HttpResponseMessage>。这就告诉我们请求将在后台执行,并且请求的结果是一个HttpResponseMessage对象。
大多数程序员感到困惑的是continuation;continuation其实就是这么一个机制:通过这个机制,你可以指定当后台任务完成时你希望发生什么事情。在上面的例子中,我们使用ContinueWith方法处理我们从HttpClient.GetAysnc方法中获取的HttpResponseMessage对象。那是如何处理的呢? 我们使用lamdba表达式,从Appress的Web服务器返回结果中获取内容的长度。请注意上面的代码中我们是使用了两次return关键字
return httpTask.ContinueWith((Task<HttpResponseMessage> antecendent) => { return antecendent.Result.Content.Headers.ContentLength; }); |
这只是令人头痛的一部分。第一个return关键字表明我们将从Task<HttpResponseMessage>对象获取返回值,这个值就是当任务完成时,返回Header的ContentLength。 ContentLength返回一个Long?结果(可空的long值),而且这GetPageLength的返回值是Task<Long?>,这也可以共方法的定义看出来
publicstaticTask<long?> GetPageLength() |
不用担心这是否有意义,因为你并不是唯一感到困惑的,而且这也只是一个简单的例子(如果例子复杂了,那么你会感到更加困惑)。复杂的异步操作可能会使用ContinueWith把大量任务链接在一起,它创建的代码不仅难以阅读还难以维护。
应用async和await关键字
微软引入了两个新的关键字到C#语言中,以简化异步方法。拿我们之前的例子来说,我们可以对其这样更改:
publicasyncTask<long?> GetPageLengthV2() { HttpClient client = newHttpClient(); var httpMessage = await client.GetAsync("http://apress.com"); // 在HTTP请求完成之前,我们可以做其他的事情 // ... return httpMessage.Content.Headers.ContentLength; } |
当调用异步方法时,我们使用await关键字。这就告诉C#编译器我们希望等待Task(执行GetAsync方法的返回)的结果的同时,执行同一个方法中的其他代码。
应用await关键字就意味着,我们可以把GetAsync方法的结果当作一个常规方法,并且我们把从GetAsync方法的结果HttpResponseMessage对象直接赋值给一个变量。此外,更好的是,我们可以像平常一样使用return关键字从方法中返回结果——在本例中,结果就是ContentLenght属性的值。这就使得方法看起来更自然,还可以让我们不在担心Continewithfangfa 和多次使用return关键字。
当我们使用await方法时,我们必须在方法的签名上添加async关键字,在我们的例子中已经添加了aync关键字。而方法的返回值并没有改变,在我们的例子中,返回值仍人是Task<Long?>。这是因为await和async通过一些巧妙的编译器技巧实现,这也就意味着它们允许我们使用更自然的语法,但是它们并不对应用了(await和async关键字)的方法中将会发生的事情做任何更改。当调用GetpageLength方法时,必须处理Task<Long?>结果,因为在后台有一个操作生产可空的long类型值——虽然,程序员还可以选择使用await和async关键字(使用了await和async关键字,也还是需要处理Task<Long?>结果。这主要是说明使用了await和async后,并不更改方法的返回结果)。
你可能会注意到我们并没有提供一个MVC的例子以测试async和await关键字。这是因为在MVC控制器中使用异步方法需要特别的技术,在17章我们将会介绍MVC的异步信息。
10 总结
在本章,我们首先概述了MVC程序员需要了解的C#语言的关键特性。这些特性结合LINQ后,将在本书的后续章节用于查询数据。正如我们说过的,我们是LINQ的大粉丝,而且LINQ在MVC中扮演重要的角色。我们还介绍了两个新关键字async和await,他们可以使异步方法变得更容易——在十七章我们会深入介绍异步技术,MVC异步控制器。
在下一章,我们将关注Razor视图引擎,通过它,可以把动态数据转换成视图。
【请尊重劳动成果、转载请注明来源】
Pro ASP.NET MVC –第四章 语言特性精华的更多相关文章
- Pro ASP.NET MVC –第六章 MVC的基本工具
在本章,我们将介绍每个MVC程序员"武器库"的三个重要工具:依赖注入容器.单元测试框架和mock工具.在本书,对于三个工具分别都只用了一种方式实现,但每个工具都还有其他的实现方式. ...
- Pro ASP.NET MVC –第三章 MVC模式
在第七章,我们将创建一个更复杂的ASP.NET MVC示例,但在那之前,我们会深入ASP.NET MVC框架的细节:我们希望你能熟悉MVC设计模式,并且考虑为什么这样设计.在本章,我们将讨论下列内容 ...
- Pro ASP.NET MVC –第五章 使用Razor
Razor是微软在MVC3中引入的视图引擎的名字,在MVC4中对其进行了改进(尽管改动非常小).视图引擎处理ASP.NET内容.寻找指令,典型地用于插入动态数据并输出到浏览器中.微软维持了两个视图引擎 ...
- ASP.NET MVC下使用AngularJs语言(一):Hello your name
新春节后,分享第一个教程. 是教一位新朋友全新学习ASP.NET MVC下使用AngularJs语言. 一,新建一个空的Web项目.使用NuGet下载AngularJs和jQuery.二,配置Bund ...
- ASP.NET MVC 4 (四) 控制器扩展
MVC的标准流程是请求传递给控制器,由控制器action方法操作数据模型,最后交由视图渲染输出,这里忽略了两个细节,就是MVC是如何创建相应控制器实例,又是如何调用控制器action方法的,这就必须讲 ...
- ASP.NET MVC 视图(四)
ASP.NET MVC 视图(四) 前言 上篇对于利用IoC框架对视图的实现进行依赖注入,最后还简单的介绍一下自定义的视图辅助器是怎么定义和使用的,对于Razor语法的细节和辅助器的使用下篇会说讲到, ...
- ASP.NET MVC 路由(四)
ASP.NET MVC路由(四) 前言 在前面的篇幅中我们讲解路由系统在MVC中的运行过程以及粗略的原理,想必看过前面篇幅的朋友应该对路由有个概念性的了解了,本篇来讲解区域,在读完本篇后不会肯定的让你 ...
- ASP.NET MVC 过滤器(四)
ASP.NET MVC 过滤器(四) 前言 前一篇对IActionFilter方法执行过滤器在框架中的执行过程做了大概的描述,本篇将会对IActionFilter类型的过滤器使用来做一些介绍. ASP ...
- 《Pro ASP.NET MVC 4》异常整理
最近在和同学一起研究毕业设计,准备模仿<Pro ASP.NET MVC 4>里面的SportsStore设计模式和测试驱动开发. 由于和书中VS版本不同,发现不少问题,在此总结. 用户代码 ...
随机推荐
- 使用的组件:Web Uploader
Web UploaderWebUploader是由Baidu WebFE(FEX)团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件.在现代的浏览器里面能充分发挥HTML5的优势, ...
- Asp.Net Web API 2第十七课——Creating an OData Endpoint in ASP.NET Web API 2(OData终结点)
前言 很久没更新博客了,加上刚过年,现在准备重新开战,继续自己的学习之路.本文已同步到Web API2系列文章中http://www.cnblogs.com/aehyok/p/3446289.html ...
- WinDbg 命令三部曲:(三)WinDbg SOSEX 扩展命令手册
本文为 Dennis Gao 原创技术文章,发表于博客园博客,未经作者本人允许禁止任何形式的转载. 系列博文 <WinDbg 命令三部曲:(一)WinDbg 命令手册> <WinDb ...
- Unity2.0容器自动注册机制
现如今可能每个人都会在项目中使用着某种 IoC 容器,并且我们的意识中已经形成一些固定的使用模式,有时会很难想象如果没有 IoC 容器工作该怎么进展. IoC 容器通过某种特定设计的配置,用于在运行时 ...
- VS2012 生成事件
在一个解决方案中有多个项目的时候,我们常需要拷贝一些文件,dll到指定的目录下,或者遇到com组件还需要提前注册dll,这个就需要用到VS的生成事件. 一.位置: 项目-->右键-->属性 ...
- [ucgui] 对话框3——GUIBuilder生成界面c文件及修改
>_<" 其实生成GUI有相应的工具:
- 不要过早退出循环 while(1){no break}
我们在尝试新的事物的时候,总是会遇到各种各样的困难,不同的人会在碰壁不同的次数之后退出.用程序员喜欢的话来说就是,我们都在for循环,区别在于你是什么情况下break的.有的人退出阈值高,这是能坚持的 ...
- nested exception is org.xml.sax.SAXParseException: cvc-elt.1: Cannot find the declaration of element 'beans'.
1缺少jar包 2spring配置文件里http://www.springframework.org/schema/beans/spring-beans-3.2.xsd的版本与实际jar包不一致
- MultiTouch————多点触控,伸缩图片,变换图片位置
前言:当今的手机都支持多点触控功能(可以进行图片伸缩,变换位置),但是我们程序员要怎样结合硬件去实现这个功能呢? 跟随我一起,来学习这个功能 国际惯例:先上DEMO免费下载地址:http://down ...
- iOS5可能会删除本地文件储存 - Caches 也不安全
转自:http://blog.163.com/ray_jun/blog/static/1670536422011101225132544/ 出处:http://superman474.blog.163 ...