第2章 基础知识

本章首先从.NET的整体结构出发,让读者认识到ASP.NET开发所需要的大环境,然后介绍了ASP.NET程序所需要的开发语言C#,以及C#语言的语法基础。通过认识大环境,了解ASP.NET程序运行的机制。通过学习C#语法,掌握ASP.NET程序开发的基础。

2.1 认识ASP.NET 3.5

本节从整体的.NET环境入手,首先介绍了最新的.NET 3.5框架,然后认识了ASP.NET在.NET框架中的位置。最后学习ASP.NET提供的一些封装好的常用类。

2.1.1 .NET Framework 3.5的功能

微软将.NET Framework(即,框架)定义为:支持生成和运行下一代应用程序和XML Web Services的内部Windows组件。.NET框架主要实现下列目标:

❑ 提供一个统一的面向对象开发环境。这个开发环境支持本地代码的开发、远程对象程序的开发,或者在本地执行但分布在Internet上的各种应用程序的开发。

❑ 更好地解决开发应用的版本和部署版本之间的冲突。

❑ 通过框架的解决方案,可以调用未知的或第三方的代码,实现已有系统的移植以及代码的可重复使用。

❑ 使开发人员在开发各种不同类型应用的时候,有一样的开发体验,如基于Windows的应用程序和基于Web的应用程序。

❑ 按照工业标准生成所有通信,以确保基于.NET Framework的代码可与任何其他代码集成。

❑ 公共语言运行库是.NET框架的基础。可以将运行库看作是一个在执行时管理代码的代理,它提供内存管理、线程管理和远程处理等核心服务,并且强制实施严格的类型安全,可提高安全性和可靠性及其他形式的代码准确性。以运行库为目标的代码称为托管代码,而不以运行库为目标的代码称为非托管代码。

❑ NET框架的另一个主要组件是类库,它是一个综合性的面向对象的可重用类型集合,可以使用它开发多种应用程序,这些应用程序包括传统的命令行或图形用户界面应用程序,也包括基于ASP.NET所提供的应用程序,如Web窗体和XML Web Services。

以上是.NET框架的基本功能,.NET Framework 3.5在.NET Framework旧版本的基础上又提供了许多功能的改进,这些新的改进如下所示。

❑ LINQ:一种集成查询语言,可支持对数据、对象等进行查询。

❑ WCF:一种服务框架,类似于早期的Remoting,但更加容易调用。

❑ WPF:一种最新的Windows Forms程序形式,用来创建更美观的窗体界面。

❑ Ajax:一个JS框架,用户B/S程序,也就是网站项目,支持客户端的快速响应。

❑ 支持分页的数据模板控件:网站项目中,添加了ListView和DataPager控件,可自定义分页数据的读取和显示。

❑ 减少客户端控件的服务器端调用:因为客户端控件变为服务器端控件,影响了客户端控件的效率,所以直接去除了此功能。

2.1.2 ASP.NET是.NET Framework的一部分

ASP.NET技术是.NET Framework技术中的一个重要组成部分,通过这个技术可以实现基于网站的应用程序的开发,由于它属于.NET框架的一部分,所以这种应用程序的开发完全可以使用.NET框架提供的各种框架技术,如编程语言、后台的.NET框架类库等。

可以使用ASP.NET网页作为Web应用程序的用户界面及后台逻辑部分。ASP.NET网页在任何浏览器或客户端设备中向用户提供信息,并使用服务器端代码来实现应用程序逻辑。ASP.NET有下列特点:

❑ 基于Microsoft ASP.NET技术,服务器上运行的代码动态地生成到浏览器或客户端设备的网页输出。

❑ 兼容所有浏览器或移动设备,ASP.NET网页自动为样式、布局等功能呈现正确的、符合浏览器的HTML。

❑ 兼容.NET公共语言运行库所支持的任何语言,其中包括Microsoft Visual Basic、Microsoft Visual C#、Microsoft J#等。

❑ 基于Microsoft .NET Framework平台,它提供了Framework的所有优点,包括托管环境、类型安全性和继承。

❑ 具有灵活性,可以在开发的时候,向页面添加用户创建的控件或第三方控件。

2.1.3 ASP.NET需要的命名空间(NameSpace)

上一节已经介绍了ASP.NET基于.NET平台,所以它会调用平台的一些命名空间。命名空间可以用来区分各个类的一个所属结构,便于将各个相似功能的对象模型统一管理。那么多命名空间,究竟哪些是ASP.NET常用的呢?表2.1列出了常用的ASP.NET命名空间。

表2.1 ASP.NET常用的命名空间

2.2 C#变量和常量

变量和常量是C#中的基本单位。通常而言,任何程序都离不开它们的参与。变量可以储存不同的值或数据,常量一般是一个固定值,如π。本节就介绍常量和变量的一些基本知识及应用。

2.2.1 系统预定义类型

预定义类型我们可能在学习C/C++的时候都学习过, C#也提供了一些预定义类型。它们一般被分为两种:预定义值类型和预定义引用类型。预定义引用类型有Object和String。C#中,Object类型是所有其他类型的基础。预定义值类型一般就是常见的bool、int、double等,详细的值类型参考表2.2。

表2.2 常见的预定义值类型

2.2.2 定义变量(标识符与关键字)

标识符(Identifier)用来表示程序中一个特定的元素。在C#中,标识符的命名有如下规则:

❑ 标识符的第一个字符必须是字母、下画线“_”或“@”;

❑ 后面的字符可以是字母、下画线或数字;

❑ 标识符的名称不能使用C#的关键字。

正确的变量名:myData、I_count、_name。错误的变量名:012a、long(关键字)、c-name(错误符号)。

注意 标识符区分大小写,name和Name是两个变量。

关键字被称为保留字,是C#中有特殊用途的一些英文单词,不能用作标识符。C#中大约70多个关键字,这些关键字都有自己的用途。表2.3列出了这些关键字。

表2.3 C#保留关键字

2.2.3 变量的初始化

变量具备固定的数据类型,还有专门的作用域。声明变量时,必须指定变量的类型。变量名一般都是小写字母,如果变量的名字比较长,可将第2个单词的首字母大写。定义变量的语法如下所示。

        int a,b              //定义变量,可同时定义多个,用逗号间隔
        int c = 0;           //定义变量,可指定变量的初始值

下面演示如何初始化变量,并输出变量最终的结果

        protected void Page_Load(object sender, EventArgs e)
        {
            int x, y;                                                              //定义变量x和y
            x = 4;
            y = 5;
            int z = 0;                                                             //定义变量z
            z = x * y;                                                             //求x和y的积
            Response.Write(z);                                                     //输出结果
        }

上述代码使用了两种变量初始化的方式,变量x是先被定义,然后才赋值。而变量z则是在定义的时候直接赋值。

2.2.4 变量的生命周期

变量的声明周期,也可以说是变量的作用域,指一个变量存在的有效期,根据有效期的长短,变量分为局部变量和全局变量。

局部变量是指在某一个阶段内此变量允许调用,而此阶段完成后,变量就被释放,再调用会发生错误。局部变量一般使用private来声明,声明语法如下所示。

        private类型 变量名;

全局变量是指变量在程序的运行期内都有效,当程序结束时,变量才会被释放。全局变量使用public来声明,声明语法如下所示。

        public类型 变量名;

2.2.5 数据的显式转换、隐式转换

在C#中,为了程序输出和数据保存的需要,不同的类型之间可以进行转换。如为了输出方便,可以将数值型转换为字符型。数据类型之间的转换可以分为显式转换和隐式转换。

(1)显式转换:在进行数据类型转换时,不需要在代码中明确指出转换后的数据类型,系统会自动进行类型判断,并正确实现类型转换。下面演示一个显式转换的过程。

        protected void Page_Load(object sender, EventArgs e)
        {
            double y = 36.232;                                //定义双精度型
            int z = (int)y;                                   //定义整型—实现显式转换
            Response.Write(z);                                //显示转换后的值-结果为36
        }

(2)隐式转换:在进行类型转换时,需要在代码中明确指出转换后的数据类型。下面的代码演示一个隐式转换的过程。

        protected void Page_Load(object sender, EventArgs e)
        {
            int x = 2;                                          //定义整型
            double z = x;                                       //定义双精度型—实现隐式转换
            Response.Write(z);                                  //显示转换后的值
        }

2.2.6 装箱和拆箱

装箱和拆箱是值类型和引用类型互相转换的过程,是数据类型转换的一种特殊应用。装箱是将值类型转换为引用类型,而拆箱正好相反,是将引用类型转换为值类型。

下面演示装箱的过程,其中object为引用类型,前面在系统预定义类型的地方已经讲到。

        protected void Page_Load(object sender, EventArgs e)
        {
            double A1 = 45.22;                                                   //定义值
            object B1 = A1;                                                      //装箱操作
            Response.Write(A1.ToString()+"-" + B1.ToString());                   //输出结果
        }

拆箱只是装箱的一个逆向操作,此时要注意类型的显式转换。下面一个例子演示了拆箱的过程。

        protected void Page_Load(object sender, EventArgs e)
        {
            double A1 = 25.3;                                                    //初始值
            object B1 = A1;                                                      //转化为引用对象后的值
            double C1 = (double)B1;                                              //将引用对象拆箱,并返回值
        }

2.2.7 字符串

字符串对象是文本处理的关键,在C#中,用String类来管理字符串。字符串是一些被双引号包装起来的文本,是一系列Unicode字符。下面的代码定义了一个字符串Name。

        string Name = "上海市浦东开发区";

注意 string关键字是小写,所有类型关键字都是小写。

在日常的开发中,字符串可以进行一系列操作,如字符串的截取、连接等,这些基本操作都通过String类提供的一些方法来完成。详细的方法及说明如表2.4所示。

表2.4 String类的常用方法

2.2.8 数组

数组由一串连续的元素组成,一般分为普通数组、动态数组和泛型数组。其中泛型将在下一小节介绍。

普通数组是最常用的集合,只能存储固定长度的数据,且数据类型必须相同。下面演示了一组长度为6的数值型数组。

        int[] myarr = new int[6] { 1,2,3,4,5,6};

注意 数组初始值使用“{ }”,且中间以逗号间隔。

如果初始化数组时不指定值,可以使用数组的索引,一次为数组中的元素赋值。注意数组的索引从0开始,代码如下所示。

        int[] myarr = new int[2];
        myarr[0] = 1;
        myarr[1] = 2

普通数组一般用于知道长度的数组,如果在程序运行时,并不知道数组的长度,则使用动态数组来保存数组元素。ArrayList用来表示动态的数组,初始化时不需要指定数组的长度。使用方法如下所示。

        ArrayList myarr = new ArrayList();            //初始化动态数组
        myarr.Add("A");                               //在动态数组中添加值
        myarr.Add("B");

2.2.9 泛型

泛型(generics)将类型参数的概念引入.NET框架,类型参数的好处是:类和方法将一个或多个类型的指定,推迟到客户端代码声明并实例化该类或方法的时候。

下面定义了一个简单的泛型方法,方法返回参数的类型,同时演示了如何对泛型方法进行调用。注意代码中传递的类型参数名为T,这不是固定的,因为它只是一个参数的名字,可以为任意值。

        public void Print<U>() //定义一个泛型方法
        {
            MessageBox.Show (typeof(U).Name);
        }
        Print<int>() ;   //调用泛型方法

泛型类和泛型方法同时具备可重用性、类型安全和效率,这是非泛型类和非泛型方法无法具备的。泛型通常用在集合和在集合上运行的方法中。

C#提供了专用于泛型集合的命名空间System.Collections.Generic,其中提供了普通数组和动态数组的泛型对象。下面来演示泛型数组的使用,定义一个返回类型是泛型数组的方法。

        private List<int> GetData()
        {
            List<int> mylist = new List<int>();
            mylist.Add("A");                                           //注意添加的是字符型数据
            mylist.Add("B");
            return mylist;
        }

下面的代码调用上面的方法,并显示泛型数组的内容。

        List<int> mylist = GetData();
        for (int i = 0; i < mylist.Count; i++)
        {
                Label1.Text = label1.Text + mylist[i].ToString();
        }

2.2.10 常量

常量一般是程序中不变的数据,如π、“北京”等,常量也有不同的类型,如π属于数值型,而“北京”属于字符型。在C#中定义常量的语法如下所示。

        public const string PEKING = "北京";

上述代码中,const是定义常量的标识符,如果没有此标识符,即使为PEKING指定固定的值,也不算是常量。string用来定义常量的类型。

注意 常量在命名时,通常使用大写字母。

2.2.11 注释

C#中的注释有多种情况,常见的分为三种:

❑ 单行代码后的注释。使用//来注释,如前面的那些代码所示。

❑ 方法前的注释。用来概括说明方法的功能和方法中的参数意义,用///表示。

❑ 对整个程序的说明。一般用在程序的开始处,说明这个文件的来源或者版权。用/*开发*/结束。

下面就是这3种注释方法的示例。

          /******************************/
          /***作者: 老李************/
          /***开发时间:2008年   **/
          /******************************/
          using System;
          using System.Collections.Generic;
          ⋯
          public partial class _Default : System.Web.UI.Page
          {
              /// <summary>
              /// 页面加载的事件
              /// </summary>
              /// <param name="sender">发送者</param>
              /// <param name="e">参数集合</param>
                protected void Page_Load(object sender, EventArgs e)
                {
                    //这里填写文档加载时的代码
                }
          }

2.2.12 C#书写规范和风格

在C#语句中,所有的空格、空行、Tab字符都会被忽略。可以想到,在一行中可以书写多条语句。因此,如下的代码和上述的using语句实例是相同的。

          using System;   using System.Collections.Generic;

但这是一种很不好的代码书写习惯,将来会直接影响代码的阅读性和维护性。各门编程语言都存在自己的编程规范。下面简单介绍C#中采用编程规范时一些通用的约定。

❑ 在同一个项目中,应该只采用一种编程风格的约定。本条规则中所说的项目,并非C#中的Project,而是现实中的一个完整的项目。

❑ 决定采用某种特定的编程风格之前,必须对编程风格进行详细的了解,并对所有参与项目的人员进行培训,以确保风格的统一。本项工作一般由项目的负责人员完成,因此,要求项目负责人员对编程风格有较深的了解。

❑ 编程风格的选取应尽量简单、合理。在不牺牲代码可读性的情况下,要把编码效率放在第一位。通常情况下,使用更多的代码是没有意义的。尤其是在一个大型项目中,提高代码的效率最重要。

2.3 运算符与表达式

运算符和表达式是C#应用程序的基础,本节除介绍它们的基本概念外,还通过代码演示了它们的应用。

2.3.1 运算符

运算符是C#进行数据处理的基础,C#中的运算符主要分为5类:算术运算符、关系运算符、逻辑运算符、赋值运算符和“?”运算符。

(1)算术运算符是常用的计算符号,如“+”、“-”、“*”、“/”等。算术运算符又分为一目运算符和二目运算符,其中一目指只有一个变量参与的运算,二目是指两个变量参与的运算。其中“+”、“-”、“*”、“/”这些运算符,必须有两个变量参与才可以实现运算,而“++”、“--”这种自增、自减的操作,只有一个变量参与。下面的代码演示了这些常用的算术运算符。

          int x = 5;
          int y = 6;
          int add = x + y;
          x++; //自增
          y--; //自减

(2)逻辑运算符一般和关系运算符结合使用。关系运算符用来比较两个数据,如“= =”、“>=”等,而逻辑运算符用来判断两个或多个使用关系运算的语句。

              int x = 5;
              int y = 6;
              if (x > y)                                               //关系运算符
                  label1.Text = (x + y).ToString();

(3)赋值运算符是C#最基本的运算,就是为某个变量指定值。“int x=5;”是一个最简单的赋值运算,“等号”左边一般为变量的名称,右边为变量的值,有时候右边也可能是另一个变量。

(4)?运算符通常被称为三目运算符,因为有三个变量参与其中。下面的代码是一个很简单的?运算。

        y = (x > 0) ? x : x++;

上述表达式中有两个关键符号“?”和“:”,其中?前面通常是一个关系运算,?后面紧跟两个变量。?运算符的意思是判断?前面的表达式,如果表达式结果为true,则选择?后面的第一个值;如果表达式结果为false,则选择?后面的第二个值,两个值之间以“:”间隔。

在前面介绍的运算符中,优先级顺序为算术运算符>关系运算符>逻辑运算符>?运算符,但这些并不代表所有的运算符。表2.5列出了常用的运算符,其中优先级顺序为从高到低。

表2.5 C#中的运算符优先级

2.3.2 表达式

表达式是可以计算且结果为单个值、对象、方法或命名空间的代码片段。表达式在C#程序中广泛应用,尤其是在计算功能中,往往需要大量的应用数学表达式。

表达式包含文本值、方法调用、运算符、操作数以及简单名称。简单名称可以是变量、类型成员、方法参数、命名空间或类型的名称。表达式可以使用运算符,而运算符又可以将表达式用作参数,或者使用方法调用,而方法调用的参数又可以被其他方法调用,因此表达式既可以非常简单,也可以非常复杂。实际上变量的初始化和赋值就是表达式的一种,如下所示。

        int x = 5;

表达式的构成可以十分复杂,下面是一个较长的表达式。

        double y= ((5+2)*3/2)^2/(4*5+9)-3;

事实上表达式可以远远复杂的多,但并不推荐程序中复杂表达式的出现。复杂的表达式将影响程序的可读性,并有可能带来难以发现的错误。

2.4 语句类型

语句又比表达式更复杂一些,可以用来表示一段流程,如今天晚上去看电影还是看话剧?这是一个选择,在程序中被称为选择语句。本节介绍几种常见的语句类型。

2.4.1 选择语句

选择语句有多种形式,可以有一种选择,还可以有多种选择。

(1)一种选择

只有一种选择的情况如下所示。如果test的值为真,则执行{ }内的语句。

          if (test)
          {
              Response.Write("我被执行了!");
          }

(2)两种选择

上述第一种选择方式如果test为假,则不执行{ }内的语句,而是执行程序其他的语句。那如果两种选择不是选择这个,就是选择那个,则表示方法如下。如果test为真,则执行第一个语句,页面会显示“我们去看电影!”;如果test为假,则执行else中的语句。

          if (test)
          {
              Response.Write("我们去看电影!");
          }
          else
          {
              Response.Write("我们去看话剧!");
          }

(3)多种选择

多种选择有两种方法,一种用if...else if...else的方式,一种用switch方式。用if这种方式的使用代码如下所示。其中的else if可以有多个。

          if (test)
          {
              Response.Write ("我们去看电影!");
          }
        else if (myBoolFalse)
          {
              Response.Write ("我们去看话剧!");
          }
        else
        {
              Response.Write("我们去看皮影戏!");
        }

switch方式的语法如下所示。这里不是判断test是否为真,而是判断test的值与{ }内的哪个case后面的值相等,如果都不相等,则执行default后面的语句。break表示中断条件判断,退出{ }这些语句。

        switch (test)
        {
            //并列的多个条件,每个case表示一个条件
            case myCondition1:
                  Operation1;
                  break;       //中断本条件的执行
            case myCondition2:
                  Operation2;
                  break;
            case myCondition3:
                  Operation3;
                  break;
            default:
                  Operation4;
                  break;
        }

2.4.2 循环语句

循环语句也有多种形式,有while语句、for语句。本节就介绍这两种常见的循环。while语句在满足条件的基础上,可以重复执行一段代码。while语句的语法如下所示。如果条件为真,就执行{ }内的语句。

        while (条件)
        {
            ...
        }

下面是一段使用while的代码。因为i等于15,而while的条件是大于5就可以,所以程序进行循环重复输出i的值,一直到不满足条件才会退出循环。

        int i = 15;                                               //初始化变量
        while (i>5)                                               //判断变量是否大于5,如果满足条件,开始循环
        {
            Response.Write(i);                                    //输出变量值
            i--;                                                  //变量自减
        }

for语句和while语句一样,也是一种循环语句,用来重复执行一段代码。两个循环的区别就是使用方法不同。for语句的使用语法如下所示。

        for (变量初始值; 变量条件; 变量步长)
        {
            循环代码段...
        }

下面将前面介绍的while循环转换成for循环,输出结果都是相同的。

for (int i = 15; i > 5; i--)

//判断变量是否大于5,如果满足条件,开始循环{

Response.Write(i);

//输出变量值}

2.4.3 跳转语句

C#中常见的跳转语句是break和continue。它们一般用于循环中,break用来中断语句的执行,而continue则是继续执行当前的循环,而后面的代码无须执行,即重新开始循环。在学习switch语句时,曾经看到过break。下面是在for语句中跳转语句的使用情况。

for(条件){

break;

continue;}

可以测试下面两段代码,看看执行效果就能明白break和continue的区别。

for (int i =15; i > 5; i--)

//判断变量是否大于5,如果满足条件,开始循环{

Response.Write(i);//输出变量值

break;

Response.Write("看看这句是否输出呢?");}

看看上面语句中最后一条语句是否执行。然后再看看下面的语句中,最后一句是否执行。

for (int i = 15; i > 5; i--)

//判断变量是否大于5,如果满足条件,开始循环{

Response.Write( i);

//输出变量值

continue;

Response.Write("看看这句是否输出呢?");}

2.4.4 异常处理语句

C#中用try...catch语句捕获程序抛出的异常。下面就是一个错误语句。

int[] myArray = new int[4]{1, 2, 3, 4};myArray[5] = 5;

//错误的赋值,将会引发异常

运行上面代码后,出现图2.1所示的错误。图中的浅色行就是错误所在。

在网站正常运行中,不可能允许用户看到这种错误,那如何捕获这些错误呢?这就用到了异常处理语句。这里学习用try...catch语句来进行处理。如下的代码演示了try...catch语句的用法。

protected void Page_Load(object sender, EventArgs e){

int[] myArray = new int[4] { 1, 2, 3, 4 };

//使用try...catch语句捕获异常。

try{

myArray[5] = 5;

//错误的赋值,将会引发异常

}

catch{Response.Write("出现了IndexOutOfRangeException异常,请检查代码,确认有无数组索引超出范围的情况!");}}

图2.1 程序运行错误

此时运行效果就不再是图2.1那样的错误,而是图2.2这样的正常提示。

图2.2 捕获错误给出提示

2.5 对象、类、接口与继承

C#是一门面向对象开发的语言,面向对象就必须了解对象和类的概念。本节主要学习对象和类的声明及应用。

2.5.1 对象和类

类是面向对象程序设计的核心,实际上是一种复杂的数据类型。将不同类型的数据和与这些数据相关的操作封装在一起,就构成了类。而将抽象化的类具体化,就成了对象,通常说对象是类的实例。

类是将现实事物抽象化,而对象是将事物具体化。如“王小丽”是一个“学生”,那么王小丽其实是比较具体的一个人,而“学生”则是一个抽象事物。

在C#中,类是一种数据结构,它可以包含数据成员(常量和字段)、函数成员(方法、属性、事件、索引器、运算符、实例构造函数、静态构造函数和析构函数)以及嵌套类型。类类型支持继承,继承是一种机制,它使派生类可以对基类进行扩展和专用化。类是使用class关键字来定义的,如下面的示例所示。

        public class Student
        {
            //类内部定义
        }

下面通过一个自己创建的学生类来演示对象和类的应用,代码如下所示:

        /// <summary>
        /// 定义一个学生类,包含两个属性:姓名和年龄
        /// </summary>
        public class Student
        {
            string _name;
            int _age;
            public string Name
            {
                  get { return _name; }
                  set { _name = value; }
            }
            public int Age
            {
                  get { return _age; }
                  set { _age = value; }
            }
        }
        protected void Page_Load(object sender, EventArgs e)
        {
            Student wangxl = new Student();                            //创建一个学生对象wangxl
            wangxl.Name="王小丽";                                      //设置对象的属性
            wangxl.Age=20;
            Response.Write(wangxl.Name);                               //显示对象的属性值
        }

2.5.2 接口

一个接口定义一个协定。实现某接口的类或结构必须遵守该接口定义的协定。一个接口可以从多个基接口继承,而一个类或结构可以实现多个接口。接口可以包含方法、属性、事件和索引器。接口本身不提供它所定义的成员的实现。接口只指定实现该接口的类或结构必须提供的成员。接口的定义与实现如下所示:

        //定义接口
        Interface test1
        {
            Method1();
        }
        //定义接口
        Interface test2
        {
            Method2();
        }
        //定义一个类,继承两个接口
        class Test: test1, test2
        {
            public Method1 () {...}
            public Method2() {...}
        }

如上面示例所示,首先定义了test1和test2接口,两个接口分别声明了Method1方法和Method2方法。类Test继承于test1和test2接口,在类的方法体中,必须实现继承接口的Method1和Method2方法。

2.5.3 继承

继承就是从父类中获取一些公开的成员,如方法和属性。继承的语法如下所示。

        class Student : Person        //继承类
        class Student : Interface1    //继承接口

子类和父类之间以“:”间隔,C#只允许继承一个父类,但允许继承多个接口。如果子类继承了接口,则必须实现接口中定义的所有公开成员。

所谓公开的成员,就是在父类中被定义为public的成员,因为public的作用域可以在子类中生效,而private的作用域则不可。

2.6 C#高级应用

本节集中介绍C#中一些比较抽象的东西,其中包括迭代器、局部类、隐式类型、Lambda表达式等。

2.6.1 迭代器

迭代器是方法、get访问器和运算符,它能够在类或结构中,支持foreach遍历而不必实现IEnumerable接口。在C#中,只需提供一个迭代器就可以遍历类中的数据结构。使用迭代器有以下优点:

❑ 迭代器是一段有序的代码,可以返回相同类型的值。

❑ 迭代器可用作方法、运算符或get访问器的代码体。

❑ 迭代器代码可使用yield return语句依次返回每个元素。yield break将终止迭代。

❑ 可以在类中实现多个迭代器。每个迭代器都必须像类成员一样有唯一的名称,并且可以在foreach语句中被客户端调用。

❑ 迭代器的返回类型必须是IEnumerable或IEnumerator。

上面的解释感觉比较枯燥,下面通过一个案例来说明迭代器的使用。

        /// <summary>
        /// 定义一个继承自IEnumerable的类
        /// </summary>
        public class DaysOfTheWeek :System.Collections.IEnumerable
        {
            //定义一周7天
            string[] m_Days = { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" };
            //定义迭代返回内容
            public System.Collections.IEnumerator GetEnumerator()
            {
                  for (int i = 0; i < m_Days.Length; i++)
                  {
                      yield return m_Days[i];
                  }
            }
        }
        protected void Page_Load(object sender, EventArgs e)
        {
            // 创建类的一个对象
            DaysOfTheWeek week = new DaysOfTheWeek();
            //用foreach遍历并输出结果
            foreach (string day in week)
            {
                  Response.Write(day + " ");
            }
        }

注意 黑体标注的yield是C#中的关键字,在迭代器块中用于向枚举对象提供值或发出迭代结束信号。

上面实例的输出结果为:

        Sun Mon Tue Wed Thr Fri Sat

2.6.2 局部类

当通过多个部分来定义类型时,将使用类型修饰符partial。为确保与现有程序的兼容性,此修饰符不同于其他修饰符:与get和set一样,它不是一个关键字,并且其后必须紧跟下列关键字之一:class、struct或interface。

局部类在aspx.cs定义中使用得非常多,在aspx页面文件建立之后,如果选择了在后台生成隐藏的代码文件,则VS会自动在后台的隐藏代码文件中生成一个局部类,用来处理页面的逻辑。每个涉及页面编程的aspx.cs页面逻辑类中,都会使用该局部类来定义。打开一个aspx.cs文件,可以看到如下的定义。

        public partial class _Default : System.Web.UI.Page

局部类并不常用,所以本节简要介绍,感兴趣的读者可以参考相关的MSDN文档。

2.6.3 隐式类型

隐式类型是自从.NET 3.0才开始有的数据类型,为了区别于其他的类型,本节没有在数据类型处讲解这个知识点。在C#中,引入了var关键字,为C#的类型转换机制提供了类型安全的保障。下面是几个常见的隐式局部变量。

        var i = 6;
        var str = " 你好";
        var d = 21.5;
        var ary=new   int[]{1,2,3,4,5};

上面的变量都使用了var关键字定义,其效果类似于下面的代码。

        int i = 6;
        string str = " 你好";
        double d = 121.5;
        int[] ary=new   int[]{1,2,3,4,5};

var的使用非常方便,但必须注意以下几点:

❑ var必须包含初始化器。

❑ 初始化器必须是一个表达式。

❑ 初始化器的编译器类型不能是null类型。

❑ 如果局部变量声明了多个声明符,这些变量必须具备相同的编译器类型。

2.6.4 对象初始化设定项

使用对象初始值设定项可以在创建对象时,直接向对象的字段或属性赋值,而无须显式调用构造函数。参考下面的例子。

        /// <summary>
        /// 定义一个学生类,包含两个属性:姓名和年龄
        /// </summary>
        public class Student
        {
            string _name;
            int _age;
            public string Name
            {
                  get { return _name; }
                  set { _name = value; }
            }
            public int Age
            {
                  get { return _age; }
                  set { _age = value; }
            }
        }
        protected void Page_Load(object sender, EventArgs e)
        {
            //对象初始化设定项
            Student wangxl = new Student { Age = 20, Name = "王小丽" };
            Response.Write(wangxl.Name);
        }

程序首先定义了一个类Student,然后为其定义了两个属性Age和Name。前面学习类的时候应该知道,如果要实例化这个类,并且为类的属性赋值,必须使用下面的语句。

        Student wangxl = new Student();                                 //创建一个学生对象wangxl
        wangxl.Name="王小丽";                                           //设置对象的属性
        wangxl.Age=20;

而使用了对象初始化设定项后,只需要下面的一行代码即可实现上述功能。

        Student wangxl = new Student { Age = 20, Name = "王小丽" };

2.6.5 类中的属性赋值自动实现

        在学习类和对象的时候,我们定义了两个属性:Age和Name,为属性赋值,使用了如下的代码。
  其中get和set内都是通用的内容,并没有非常特殊的逻辑处理。这时就可以使用.NET 新增的属性赋值
  自动实现的方法。
        //定义姓名属性
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

下面就是上述代码变为自动实现属性后的结果。不再带{ },而是直接用分号代替。

        public string Name    //姓名属性
        {
            get;
            set;
        }
        public int Age    //年龄属性
        {
            get;
            set;
        }

2.6.6 Lambda表达式

Lambda表达式是一种简约化表述匿名方法的函数表达式。其表述语法如下所示,有点类似C++的指针。

        Param => Expr;

通过上面这个表达式可以看出,一个Lambda表达式的组成通常如下:

❑ 一个参数或参数列表,也就是输入变量。

❑ =>符号,称作Lambda运算符,MSDN中将这个符号念作goes to。

❑ Lambda语句块,可以是单条语句也可以是多个语句的语句块。

下面是一个简单的例子。首先定义了一个数组,其中包含10个整数。然后使用了“n=> n%2 == 1”这个表达式来简化了判断是否被2整除的操作。

        int[] numbers = { 6, 5, 7, 5, 7, 2, 6,8, 9,10 };
        int oddNumbers = numbers.Count(n=> n%2 == 1);

注意 Lambda表达式在LINQ查询中应用非常广泛,后面会单独介绍LINQ。

2.7 小结

本章从C#的基础语法入手,学习了C#的常量和变量,然后学习了运算符和表达式,从最简单的变量,然后到一条语句,再到多条语句,并介绍了这些语句的类型,如循环、选择语句等。因为C#是一门面向对象的语言,所以本章也简要介绍了C#面向对象开发的主要基础,即类和对象。因为本书并不是专门针对C#的书籍,所以讲解方式非常简单,重点是让读者有个学习的过渡。本章最后还介绍了C#语言中一些非常特殊的用法,其中包括了.NET 3.5中特有的Lambda表达式等。