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视图引擎,通过它,可以把动态数据转换成视图。
【请尊重劳动成果、转载请注明来源】