第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表达式等。