创建一个新的MVC项目,命名为LanguageFeatures,在Controller文件夹中新建HomeController控制器,修改Index动作方法,只返回一个字符串。在Views/Home文件夹下添加一个名为Result.cshtml的强类型视图,模型类设置为String,用于展示Index动作方法的结果。如下图所示。
一:使用自动实现的属性
C#的属性特性能够暴露一个类的数据片段,这些数据与如何设置和接收数据采取了一种松耦合的方式。在LanguageFeatures项目Models文件夹下新建一个类名为Product,如下图。
Public string Name表示名称为Name的属性。其get代码块(称为getter)中的语句在读取该属性值时执行,而set代码块(称为setter)中的语句在对该属性赋值时执行(特殊变量value表示要赋的值)。属性时其他类来使用的,就好像它是一个字段一样,它显示了添加到Home控制器中的AutoProperty动作方法,如下图。
可以看出,属性值的读取和设置就像对一个规则的字段进行操作一样。使用属性要比使用字段更好,因为用户可以修改get和set块中的语句,而不需要修改依赖于这个属性的整个类。
启动调试,导航到/Home/AutoProperty,可以看到输出结果为:“产品名称:铅笔”。
当一个类有很多属性时,事情就有点乏味了,所有这些都要协调访问字段。于是需要做一些不必要的冗长的事情,如下图所示。
我们希望属性具有灵活性,但此刻又不需要自定义的getter和setter。解决方法时使用一种自动实现的属性,也成为“自动属性(Automatic Property)”。利用自动属性,可以创建字段支持式属性(Field-backed Property)模式,而不需要定义这个字段或在getter和setter中指定代码,如下图所示。
在使用自动属性时,有两个关键点需要注意。第一时不定义getter和setter的实现体,第二时不定义由该属性返回的字段。这两者都由C#编译器在这个类被建立时自动完成。使用自动属性与使用规则属性没什么不同。通过使用自动属性可以减少一些输入,形成更易于阅读的代码,并且仍然保持了属性的灵活性。如果需要改变一个属性的实现方式,还可以返回到规则属性的格式(必须同时实现getter和setter)。如下图所示
修改Home控制器下的AutoProperty动作方法如下图,执行调式可得到输出为“产品名称:12铅笔”。
二:使用对象与集合的初始化器
另一个无聊的编程任务时构造一个新的的对象,然后给属性赋值,如下图所示。
必须通过三个步骤来创建一个Product对象并产生结果:创建对象,设置属性值,调用View方法,以便能够通过视图显示其结果。精简步骤,我们可以使用对象初始化器(Object Initialiter)特性,一步到位地创建并填充Product实例,如下图所示。
Product名字之后调用的花括号形成了初始化器,用户可以用它作为参数提供值,以作为该对象的构造过程。以这种同样特性作为构造过程,也能对集合和数据的内容进行初始化,如下图所示。
上图演示了如何构造并初始化一个数组和两个泛型集合库中的类。这种特性时一种语法便利——它只是使C#用起来更舒适,并没有任何其他影响或好处。
三:使用扩展方法
扩展方法(Extension Method)是给那些不是你拥有、因而不能直接修改的类添加方法的一种方便的方法。添加到Models文件夹一个ShoppingCart类,它表示了一个Products集合,如下图所示。
这是一个很简单的类,它的作用是封装一个Product对象的列表。假设需要能够确定这个ShoppingCart类中Product对象的总价格,但不能对这个类进行修改,可以用一个扩展来获得所需要的功能。新建一个MyExtensionMethods类,如下图所示。
第一个参数前的this关键字吧TotalPrices标记为一个扩展方法,它告诉.NET,这个扩展方法运用于哪个类——此例为ShoppingCart类。可以用cartParam参数来引用ShoppingCart类的实例,ShoppingCart类是这个扩展方法所运用的类。该扩展方法枚举了ShoppingCart中的所有Product,并返回Product.Price属性的总和。在Home控制器添加一个名为UseExtension的新动作方法,如下图所示。
我们可以在ShoppingCart对象上调用TotalPrices方法,就好像它是ShoppingCart类的一部分一样,尽管它是由一个完全不同的类定义的扩展方法。如果扩展类与当前类在同一命名空间下,或是在using语句子项的命名空间之中,.NET便会找到它们。启动调试,会得到输出为“产品总价格:¥378.40”。
3.1 对接口运用扩展方法
也可以创建运用于一个接口的扩展方法,它允许在实现这个接口的所有类上调用这个扩展方法。修改ShoppingCart类,如下图。
对扩展方法进行更新,以使它能够处理IEnumberable<Product>,如下图所示。
第一个参数类型改为IEnumberable<Product>,它使方法中的foreach循环直接对Product对象进行操作,在其他方面,该扩展方法未作改变。切换成接口意味着可以对任何IEnumberable<Product>所枚举的Product对象进行总价计算,IEnumberable<Product>不仅包括了ShoppingCart实例,而且也可以使Products数组。在Home控制器下新建名为UseExtensionEnumberable的动作方法,如下图所示。启动调式,将会看到输出结果为“购物车产品总价格:¥378.40,数组产品总价:¥378.40”。
3.2 创建过滤扩展方法
本例演示的过滤扩展方法使对对象集合进行过滤。这是一种对IEnumberable<T>进行操作且也返回一个IEnumberable<T>结果的扩展方法,可以用yield关键字把选择条件运用于原数据中的数据项,以产生一个结果子集。在MyExtensionMethods类中添加一个名为FilterByCategory的扩展方法,如下图所示。
这个方法采用了一个附加参数,它允许在调用该方法时注入一个过滤条件。那些Category属性与该参数匹配的Product对象将以IEnumberable<Product>形式被返回,而不匹配的被丢弃。在Home控制器中添加名为FilterByCategory动作方法,如下图所示。
当调用FilterByCategory扩展方法时,只有Soccer分类中的那些Peoducts被返回。启动调试,会得到类别为Soccer的产品价格总和输出:“产品总价格:¥54.45”。
四:使用Lambda表达式
我们可以使用一个委托,以使上述FilterByCategory风法更通用。这样,正对每个Product进行调用的委托,可以以所选择的方式来过滤对象。在MyExtensionMethods类中添加一个名为Filter的扩展方法,如下图所示。
此方法使用了一个Func作为过滤参数,这意味着不需要把这个委托定义为一个类型。该委托有一个Product参数,并返回一个布尔值,如果Peoduct应该被包含在结果中,则返回True。在Home控制器中创建UseFilterExtensionMethod动作方法,如下图所示。
现在,可以用这个委托中指定的任何条件来过滤Product对象了,但必须为要用到的每一种过滤定义一个Func,这是不理想的。比较简洁的方式是使用Lambda表达式,它是以委托的方式表示一个方法体的一种简洁格式。可以采用它来替换委托定义,如下图所示。
Lambda表达式选中显示。参数用不指定类型的方式表示,其类型由自动推理去完成。“=>”字符读作“转给(goes to,或进入)”,并把这个参数链接到lambda表达式的结果。在上例中,名为“pro”的Product参数转给一个布尔结果,如果pro的Category参数等于Soccer,则返回true。甚至可以完全去掉Func,形成更紧凑的语法,如下图所示。
直接以lambda表达式作为传递给Filter方法的参数。这是最简洁自然的表示过滤的方式。此外,还可以通过扩展lambda表达式结果部分的方式来组合多个过滤,如下图所示。
修改后的lambda表达式将匹配Soccer分类或Price属性值大于20的Product对象。
五:使用自动类型接口
C#的var关键字允许用户定义一个局部变量,而不必明确指出该变量的类型,这称为类型推断(Type Inference)或隐式类型(Implicit Typing)。如下图所示。
并不是myVariable没有类型,只是本例要求编译器通过代码来推断它。通过随后的语句可以看到,编译器将只允许调用这个推断类——Product 的成员,Count不属于推断类型的属性,故会出现编译时错误。
六:使用匿名类型
通过结合对象初始化器和类型推断,可以创建简单的数据存储对象,而不需要定义相应的类或结构。如下图所示。
myAnonType是一个匿名对象,这并不意味着它的类型时动态的,JavaScript变量在这种情况下才是动态类型,这只是表示该类型的定义将由编译器自动创建,强类型仍然是必须的。用户只可以获取和设置初始化器中已经定义的那些属性。
C#编译器会基于初始化器中已经定义的参数名和类型来生成这个类。具有相同属性名和类型的两个匿名类型对象,将被分配给同样的自动生成的类。即,可以创建匿名类型对象的数组。在Home控制器中添加一个名为CreateAnonArry动作方法,如下图所示。
我们用了var声明了变量数组,这么做时必须的,因为没有像在规则的类型化数组中那样指定类型。即使没有对这些对象定义一个类,也仍然能够枚举这个数组的内容,并从每个条目中读取Name属性的值。如果没有这一特性,将无法创建匿名对象的数组。或者更确切地说,可以创建这个数组,但不能用它做任何事情。
七:执行语言集查询
目前为止,前文介绍的所有语言特性都能在LINQ特性中排上用场。LINQ是在类中查询数据的一种类似SQL的语法。假设有一个Product对象的合集,要找出其中三个最高价,并将它们传递给View方法。若使用传统方式,将会写成如下图所示的代码。
如果利用LINQ,可以明显简化这一查询过程,如下图所示。
选中部分类似于SQL查询,它对Product对象进行降序排序,并用select关键字返回一个匿名类型,而且包含了Name和Price属性。LINQ的这种风格称为“查询语法(Query Syntax)”。刚开始用LINQ时,这是最舒服的一种语法。这种查询的缺点是,它为查询数组的每个Product返回一个匿名类型对象,因此,需要对此结果进行处理,以获得最初的三个最高价。
然而,如果我们愿意放弃这种查询语法的简单性,可以得到功能强大得多的LINQ。可用得方法是点符号法(Dot-notation Syntax)或点符号(Dot Notation),这是基本扩展得方法。修改前文代码,如下图所示。
OrderByDescending方法重组了数据源中国得条目,lambda表达式返回用户想要比较得值,Take方法返回结果最前面得指定条目。Select方法允许用户对结果进行设计,指定想要得结果。这里设计了一个匿名对象,它包含了Name和Price属性。
八:使用Async方法
在.NET4.5中,对C#得一个最大补充是改善了异步方法得处理方式。异步方法在后台执行和工作,并在工作执行完成时通知用户,这允许在后台工作执行期间,使代码能够处理其他业务。异步方法使消除代码(性能)瓶颈的一个重要工具,并让应用程序能够利用多处理器和处理器内核并行地处理工作。
在Models文件夹下添加名为“MyAsyncMethods”的新类,在此类中添加一个名为GetPageLength的方法,如下图所示。
此例使用了System.Net.Http.HttpClient对象,以请求Apress主页的内容并返回它的长度。选中部分容易引起混淆,它是此例的任务延续(Task Continuation)。
.NET将异步完成的工作表示一个Task(任务),Task对象是强类型,它取决于后台工作所产生的结果。因此,当调用HttpClient.GetAsync方法时,所得到的是一个Task<HttpResponseMessage>。这样可以得知,该请求将在后台执行,且请求结果是一个HttpResponseMessage对象。
容易混淆的部分是“延续”,这是一种机制,通过该机制你可以指定后台任务完成时希望发生的事情。在上述示例中,用ContinueWith方法处理了HttpClient.GetAsync方法所得到的HttpResponseMessage对象,用lambda表达式返回了Apress Web服务器所得到的内容长度属性的值。return关键字使用了两次,第一个return关键字指明这是在返回一个Task<HttpResponseMessage>对象,当任务完成时,它将返回(第二个return)ContentLength报头的长度。ContentLength报头返回的是一个long?结果,这表明GetPageLength方法的结果是Task<long?>。
运用async和await关键字
微软对C#引入了两个新的关键字,用于简化HttpClient.GetAsync这类异步方法的使用。这两个关键字就是async和await,使用这两个关键字,修改GetPageLength方法,如下图所示。
在调用异步方法时,这里使用了await关键字,这是告诉C#编译器,要等待GetAsync方法所返回的Task结果,并继续执行同一方法中的其他语句。
运用await关键字意味着,可以将GetAsync方法看成一个常规方法,而且只是将其返回的HttpResponseMessage对象赋给一个变量。因此能够以正常的方式使用return关键字产生其他方法的结果——此例是ContentLength属性的值。
当使用await关键字时,也必须对该方法的签名添加async关键字。