首页 > 代码库 > 【Asp.net入门2-01】C#基本功能

【Asp.net入门2-01】C#基本功能

C#是一种功能强大的语言,但并不是所有程序员都熟悉我们将在本书中讨论的所有功能。因此, 本章将介绍优秀的Web窗体程序员需要了解的C#语言功能

本章仅简要介绍每一项功能。有关C#语言本身的知识不是本课程讲述的重点(本课程重点讲述Asp.net技术)。读者需要在学习本门课程之外,利用空余时间去学习C#语言基础知识。推荐几本参考书籍:

  • 《C#4.0图解教程》:https://yunpan.cn/ck7CeQGc5naFD (提取码:ffc7)
  • 《C#编程语言与面向对象基础精简教程》:https://yunpan.cn/ck7hugfgDPPHi (提取码:6b42

1.创建示例项目

为了演示语言功能,本节创建了一个名为LanguageFeatures的新的ASP.NET Empty Web Application (ASP.NET空Web应用)项目。 为了演示不同的功能,在项目中添加了一个Web窗体,以便添加新的Default.aspx文件,该文件的内容如代码清单3-1所示。

 代码清单3-1 Default.aspx文件的初始内容

技术分享

代码为该页面添加了一个标题,并用一些标准HTML元素替换了默认的窗体条目,这包括一个p 元素,其中包含插入计算GetMessage方法的结果的代码片段。在Default.aspx.cs代码隐藏文件中定义了 GetMessage方法,该文件的内容如代码清单3-2所示。

技术分享

这是一个非常简单的起点。启动应用程序后,Web窗体生成的HTML如图3-1所示。

技术分享

技术分享思考:1.如果在Default.aspx.cs文件中没有定义GetMessage方法,运行Default.aspx页面会不会出错?2.如果Default.aspx.cs代码修改成如下代码:using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.UI;using System.Web.UI.WebControls;using System.Text;using System.Threading.Tasks;namespace LanguageFeatures {    public partial class Default : System.Web.UI.Page {        protected void Page_Load(object sender, EventArgs e) {        }    }    public class MyDefault {        protected string GetMessage()        {            return "Hello,This is a Web Form";        }    }}此时访问Default.aspx页面,是否会出错?为什么?

2.使用自动实现的属性

属性是C#类中的一种成员,有关属性的介绍请参考:http://www.runoob.com/csharp/csharp-property.html。

下面要讲述的是如何实现自动属性和为什么要使用自动属性。

在 LanguageFeatures项目中添加一个Product类,该类中包含了一个简单的示例,如代码清单3-3所示。 (要创建新类,请在Solution Explorer中右键单击LanguageFeatures项,然后从弹出式菜单中选择Add →Class。将名称设置为Product.cs,然后单击Add按钮创建该文件。)

 技术分享

这个Name属性以粗体显示。将在读取该属性值时执行get代码块(称为getter)中的语句,在为该 属性分配值(特殊变量value表示分配的值)时执行set代码块(称为setter)中的语句。 属性将由其他类使用,就好像它是一个字段一样,如代码清单3-4所示,它演示了如何使用 Default.aspx.cs代码隐藏类中的属性。

技术分享

如上所示,代码读取并设置了属性值,就像它是一个常规字段一样。使用属性要优于使用字段, 因为你可以更改get和set代码块中的语句,而无需更改所有依赖该属性的类。 启动项目,即可看到此示例的效果。由于仅使用了Default.aspx Web窗体来显示GetMessage方法返 回的字符串,因此将以文本而不是屏幕截图的形式显示结果。启动应用程序后,浏览器显示的消息如 下所示:

Product name:Kayak 

很好!但是,如果类包含许多属性,所有这些属性都只是间接访问某个字段,那么代码就会变得 冗长起来,最终导致代码过于臃肿,看看代码清单3-5就知道了:

技术分享

使用属性是一个不错的做法,因为随后你可能需要更改获取和设置值的方式,但这种灵活性也生 成了大量低效、难以阅读的代码。解决这个问题的办法是使用自动实现属性,也称为自动属性。使用 自动属性,可以摆脱冗余代码,即创建一种支持字段的属性模式,如代码清单3-6所示。

技术分享

在使用自动属性时,需要注意几个要点。首先,不必定义getter和setter的主体。其次,不必定义 支持属性的字段。这两项内容将在构建类时由C#编译器定义。自动属性与常规属性并无不同,在代码 清单3-4中,在代码隐藏类里的代码仍然有效,无需任何修改。 使用自动属性,减少了输入量,创建了更易于阅读的代码,并且仍然保留了属性提供的灵活性。 如果需要更改实现属性的方式,则可以返回到常规属性格式。假设需要更改处理Name属性的方式,如 代码清单3-7所示。

技术分享

技术分享注意:
必须实现getter和setter才能返回到常规属性。C#不支持在一个属性中混合使用自动格式和常规格式的getter和setter。

3.使用对象和集合初始化器

另一个繁琐的任务是构建一个新对象,然后单独为它的属性分配值,如代码清单3-8所示,其中显示了对Default.aspx.cs代码隐藏类所做的更改。

 技术分享

必须完成3个步骤才能创建Product对象并生成结果:创建对象,设置参数值,然后返回希望插入 到由Default.aspx Web窗体返回的HTML中的值。可以使用C#对象初始化器功能,在单个步骤中创建并填充Product实例,从而减少这其中的一个步骤,如代码清单3-9所示。

技术分享

Product名称调用后的大括号({})即为初始化器,用来在构造过程中为参数提供值。每个分配以 逗号隔开,并且不需要以任何方式给正为其分配值的属性名添加前缀。(换言之,为ProductID而不是 myProduct.ProductID分配值。) 与自动属性很像,这是一项由编译器实现的功能,它使C#代码更易于阅读和编写。如果启动应用 程序,将在浏览器中看到以下消息:

Category: Watersports 

该功能可以在构造过程中初始化集合和数组的内容,如代码清单3-10所示。

 技术分享

代码清单演示了如何构造和初始化泛型集合库中的一个数组和两个类。运行该示例,浏览器中将 显示以下消息:

Fruit:orange 

这项功能只是强化了语法——提高了C#的易用性,没有任何其他影响。

4.使用扩展方法

扩展方法可以向不为你所有或无法直接修改的类中添加方法。这里在新类文件ShoppingCart.cs中定义了ShoppingCart类(购物车类),该类的内容如代码清单3-11所示。ShoppingCart表示Product对象的集合。

using System.Collections.Generic;using System.Collections;namespace LanguageFeatures {    public class ShoppingCart : IEnumerable<Product> {        public List<Product> Products { get; set; }    }}

这是一个非常简单的类,它对Product对象的List进行了包装。(本例中,只需要一个基本类。)假 设需要确定ShoppingCart类中Product对象的总值,但由于该类来自第三方并且手头没有源代码,因而 无法修改该类,这时可以使用扩展方法获得所需的功能。代码清单3-12显示了MyExtensionMethods类, 它是在MyExtensionMethods.cs这个新类文件中定义的。

技术分享

扩展方法必须为静态,并且必须在静态类中定义。第一个参数前面的this关键字将TotalPrices 标注为扩展方法。第一个参数告诉.NET可以将扩展方法应用于哪个类——本例中为ShoppingCart。 cartParam参数可以引用已对其应用扩展方法的ShoppingCart的实例。TotalPrices方法将枚举 ShoppingCart 中 的 Product ,并返回 Product.Price 属性的总和。代码清单 3-13 显示了如何在 Default.aspx.cs代码隐藏类中应用扩展方法。

技术分享注意:扩展方法不允许打破类为其方法、字段和属性定义的访问规则。你可以使用扩展方法扩展某个类的功能,但只能使用有权访问的类成员。

技术分享

其中的关键语句如下所示:

decimal cartTotal = cart.TotalPrices();

如你所见,代码对ShoppingCart对象调用了TotalPrices方法(实际上是其他类定义的扩展方法), 就好像它是ShoppingCart类的一部分。如果扩展类处在相关作用域内,也就是说,扩展类和调用扩展 方法的类属于同一命名空间,或处在using语句主体的命名空间中,则.NET将会查找扩展类。以下是 结果:

Total:$378.40 

这里使用了因位置而异的货币字符串格式,它会显示本地货币符号以及金额。这意味着,将根据 你所在的位置显示结果,你可能看不到显示的美元符号。

5.将扩展方法应用于接口

还可以创建应用于接口的扩展方法,这样就可以对实现接口的所有类调用扩展方法。有关接口的定义和基本用法请参考:http://www.runoob.com/csharp/csharp-interface.html。将扩展方法应用于接口的代码如代码清单3-14 ,其中更新了ShoppingCart类,以实现IEnumerable接口:

代码清单3-14在ShoppingCart类中实现接口

using System.Collections.Generic;using System.Collections;namespace LanguageFeatures {    public class ShoppingCart : IEnumerable<Product> {        public List<Product> Products { get; set; }        public IEnumerator<Product> GetEnumerator() {            return Products.GetEnumerator();        }        IEnumerator IEnumerable.GetEnumerator() {            return GetEnumerator();        }    }}

 现在,可以更新扩展方法,以便处理IEnumerable,如代码清单3-15所示,其中显示了 MyExtensionMethods类的更改情况。

技术分享

将参数类型更改为IEnumerable,这意味着方法主体中的foreach循环将直接作用于 Product对象(第4小节中是作用于cartParam.Products)。将参数更爱为接口,意味着可以计算由任何IEnumerable返回的Product对象的总值, 这包括ShoppingCart的实例以及Product对象的数组,如代码清单3-16所示。

代码清单3-16 将扩展方法应用于同一接口的不同实现

using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.UI;using System.Web.UI.WebControls;using System.Text;using System.Threading.Tasks;namespace LanguageFeatures{    public partial class Default : System.Web.UI.Page    {        protected void Page_Load(object sender, EventArgs e)        {        }        protected string GetMessage()        {            IEnumerable<Product> products = new ShoppingCart            {                Products = new List<Product>                {                    new Product {Name = "Kayak", Category = "Watersports", Price = 275M},                    new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},                    new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},                    new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}                }            };            Product[] productArray = {                            new Product {Name = "Kayak", Category = "Watersports", Price = 270M},                new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},                new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},                new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}            };            decimal cartTotal = products.TotalPrices();            decimal arrayTotal = productArray.TotalPrices();            return String.Format("Cart Total:{0:c},Array Total:{1:c}", cartTotal, arrayTotal);                    }    }}

 

输出的网页上显示结果为:

Cart Total:¥378.40,Array Total:¥373.40

这表示无论以何种方式收集Product对象,扩展方法都会正确调用TotalPrices()方法返回正确结果。

6.使用 lambda 表达式

委托(delegate)可以针对每个Product调用委托, 从而便于以任意方式过滤对象,如代码清单3-19所示,将Filter的扩展方法添加到MyExtension Methods类中。

using System.Collections.Generic;using System;namespace LanguageFeatures {    public static class MyExtensionMethods {        public static decimal TotalPrices(this IEnumerable<Product> productEnum) {            decimal total = 0;            foreach (Product prod in productEnum) {                total += prod.Price;            }            return total;        }public static IEnumerable<Product> Filter(              this IEnumerable<Product> productEnum, Func<Product, bool> selectorParam) {            foreach (Product prod in productEnum) {                if (selectorParam(prod)) {                    yield return prod;                }            }        }    }}

将Func用作过滤参数,这表示不需要按类型定义委托。该委托接受一个Product参数并返回一个 bool值,判断条件是:如果此Product包含在结果中,则该值为true,否则为false。

使用上述这种方式实现过滤功能的话,代码需要做如下修改,如代码清 单3-20所示,其中显示了对Default.aspx.cs代码隐藏类所做的更改。

技术分享

这里更进了一步,从这种意义上说,现在可以使用在委托中指定的任何标准过滤Product对象,但 必须为所需的每一种过滤定义一个Func,这样做并不理想。较为简单的替代方法是使用lambda表达式, 它采用一种简洁的格式来表达委托中的方法主体。该表达式可以替换委托定义,如代码清单3-21所示。

代码清单3-21 用lambda表达式替换委托定义

using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.UI;using System.Web.UI.WebControls;using System.Text;using System.Threading.Tasks;namespace LanguageFeatures {    public partial class Default : System.Web.UI.Page {        protected void Page_Load(object sender, EventArgs e) {        }        protected string GetMessage() {            IEnumerable<Product> products = new ShoppingCart            {                Products = {                                new Product {Name = "Kayak", Category = "Watersports", Price = 275M},                    new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},                    new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},                    new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}                }            };            Func<Product, bool> myFilter = prod => prod.Category == "Soccer";            decimal total = products.Filter(myFilter).TotalPrices();            return String.Format("Soccer Total:{0:c}", total);                   }    }}

lambda表达式以粗体显示。在表示参数时不需要指定类型,类型将被自动推导出来。=>字符意为 “转到”,它会将参数链接到lambda表达式的结果。在示例中,Product的参数prod链接到了一个bool结 果,如果prod的Category参数等于Soccer,则该结果将为true。你可以完全取消Func,使语法更紧凑, 如代码清单3-22所示。

代码清单3-22 没有Func的lambda表达式

using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.UI;using System.Web.UI.WebControls;using System.Text;using System.Threading.Tasks;namespace LanguageFeatures {    public partial class Default : System.Web.UI.Page {        protected void Page_Load(object sender, EventArgs e) {        }        protected string GetMessage() {            IEnumerable<Product> products = new ShoppingCart            {                Products = {                                new Product {Name = "Kayak", Category = "Watersports", Price = 275M},                    new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},                    new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},                    new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}                }            };            decimal total = products.Filter(prod => prod.Category == "Soccer").TotalPrices();            return String.Format("Soccer Total:{0:c}", total);                   }    }}

这个示例中将lambda表达式作为Filter方法的参数。这是展示过滤器的一种简洁、自然的表达方 式。可以通过扩充lambda表达式的结果部分来组合多个过滤器,如代码清单3-23所示。

using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.UI;using System.Web.UI.WebControls;using System.Text;using System.Threading.Tasks;namespace LanguageFeatures {    public partial class Default : System.Web.UI.Page {        protected void Page_Load(object sender, EventArgs e) {        }        protected string GetMessage() {            IEnumerable<Product> products = new ShoppingCart            {                Products = {                                new Product {Name = "Kayak", Category = "Watersports", Price = 275M},                    new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},                    new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},                    new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}                }            };            decimal total = products.Filter(prod => prod.Category == "Soccer" || prod.Price > 20).TotalPrices();            return String.Format("Soccer Total:{0:c}", total);                   }    }}

这个修订的lambda表达式将匹配属于Soccer类别或Price属性大于20的Product对象。

7.使用自动类型推断

C# var关键字可用于定义本地变量,而无需指定变量类型,如代码清单3-24所示。这称为类型推 导或隐式类型。

技术分享

 

并不是myVariable没有类型,这只是要求编译器从代码中推导其类型。从随后的语句可以看出, 编译器仅允许调用所推导的类的一个成员——本例中为Product。

8.使用匿名类型

结合对象初始化器和类型推导,可以创建简单的数据存储对象,而无需定义对应的类或结构。代 码清单3-25提供了一个示例。

技术分享

在这个示例中,myAnonType是一个匿名类型对象。这不是说它是动态的,在这种情况下,JavaScript 变量才是动态类型。这只是表明,类型定义将由编译器自动创建。例如,只能获取和设置已在初始化 器中定义的属性。此示例将在浏览器中生成以下消息:

Name:Kayak, Type:Watersports 

其它有关C#在实际开发中的技巧和特性等到用到的时候再向大家解释,因为对初学者来说,再往下写下去会越看越糊涂了。

【Asp.net入门2-01】C#基本功能