C#学习笔记第三发---进阶特性
2021-02-19 05:19
一、异常处理机制
编写的程序在编译不报错之后并不是就不会出错了,在运行时由于逻辑问题或者别的原因还是可能出现各种异常,异常处理机制就是为了处理这种情况。异常处理中需要用到三个关键字,try、catch、finally。其中try下的大括号内写可能出现异常的代码块,catch下的大括号写异常的处理方式(catch需要有输入参数,参数就是异常类,在不确定异常种类时可以用Exception类,因为这是所有异常类的父类),finally下大括号写可能异常的代码块运行结束后的代码,比如释放空间等,写在finally外则在出现异常时不会运行,必须写在finally内。还有一个关键字是throw,用于抛出异常。一个try可以有多个catch,会根据异常类型的设置跳入到相应的catch内,类似于重载。异常处理户占用大量的系统资源,要尽量减少。
二、I/O操作
I/O操作是指文件读写或者是硬盘上的读写,相关的有文件的读写,路径的操作和文件夹的操作。常用的类有File和Directory两个静态类,File用于检查文件(夹)是否存在、复制、 移动、 重命名、 创建、 打开、 删除和一次打开单一文件等操作,Directory用于通过目录和子目录进行创建、移动和枚举等操作。如果要创建实体类,则要用到FileInfo和DirectoryInfo这两个实体类,FileInfo类提供创建、复制、删除、移动和打开文件的实例方法,并且可以帮助创建FileSteam对象,DirectoryInfo类用于创建、移动和枚举目录和子目录的实例方法。写文件时有两种方式,一种是先建立FileStream类,定义文件的文件名和操作,使用BinaryWriter类实例化一个方法,来进行写入,具体代码如下:
FileStream fs=new FileSteam(FileName,FileMode.Create);
BinaryWrite w=new BinaryWrite(fs);
w.Write("a");
w.close();
fs.close();
另一个方法就是使用StreamWrite方法建立实体类,使用类下的方法Write和WriteLine写入,具体实现如下:
StreamWrite w=File.AppendText("FileName.txt");
w.WriteLine("Hello");
w.Flush();
读操作类似于写操作,只是使用的类是FileReader,使用的方法是ReadLine,具体实现类似于写操作。
三、索引器
Indexer索引器可以让class想数组一样访问每个元素,相当于重写了一个实体类的this方法,所以索引器必须使用this命名。索引器包括get和set方法,实现如下:
public string this[int index]
{
get
{
string tmp;
if(index>=0&&index
{
tmp=nameList[index];
}
else{
tmp="";
}
return tmp;
}
set
{
if(index>=0&&index
{
nameList[index]=value;
}
}
}
在main函数中,调用建立实力类数组的方法后,就可以默认的使用索引器进行读写操作。索引器中不强制规定索引类型和返回值类型,不同的索引器可以通过改变索引类型的方法重载,类似于方法的重载,输入参数数目的改变也可以重载(用于多维数组)。
索引器还可以应用于接口,在接口中索引器的get和set不需要写具体实现,在继承他的class中再实现具体方法
四、委托
委托相当于一个函数指针,需要在main函数外声明,声明格式为:delegate 返回值类型 委托名(参数类型 参数名)。然后再main函数中对委托实例化,直接使用委托名new一个新的实例化对象,在new时要代入符合委托定义时的参数类型的方法(方法写在main外,可以使用静态方法或者实例化的方法),然后使用实例化的委托实现方法。使用静态方法时,实例化委托时可以直接调用方法,使用实例化方法内的委托时,要先建立实例化对象的实例,然后调用委托,调用时参数写:实例名.方法。同一个委托可以调用多个方法。使用委托可以实现多重委托(Multi-casting delegatte),使用+或者-将多个委托连接后赋值一个新的委托,新的委托会按照添加顺序顺序执行每个委托,-的时候不必严格按照添加顺序,会根据添加顺序取消最后一个添加的该委托,-一个不存在于列表的委托不会造成任何影响。
五、事件
常用语图形化交互界面中,用于通知程序发生了一些事件(比如鼠标点击,键盘输入等),便与程序下一步执行。事件编写在Windows Forms Application而不是Console Application。使用工具箱内的工具可以方便的添加各种功能,双击就可以编写依存于添加按钮或者框体的事件。使用控制台编写时事件和委托要一起命名,先定义委托,定义事件时,要在事件名之前加入委托名,表示该事件可以触发这个委托。使用event关键字命名事件,在main函数中通过+=将具体的委托绑定到事件上。event可以被继承,也可以在interface的实现类里实现。事件内要写实现类,来判断事件是否绑定了委托,非空则执行。与事件绑定的委托推荐的写法是:
public delegate void 委托名(object sender,EventArge e),其中object类表明出发的条件,EventArge表示与事件相关的一些参数,C#自身有一个方法EventHandler,可以在定义事件时,使用这个方法来规范化定义的事件
六、泛型
泛型在对不同数据类型的数据进行相同的操作时可以省去重复代码。在定义一个class时,在class名后加
七、Attribute
Attribute用来添加附加的声明信息,C#自带了一些Attribute。
1、Conditional
在方法前写[Conditional("DEBUG")],则只有在debug模式下或者在程序前注明#define DEBUG的情况下,才会运行此类中的方法。
2、Obsolete
在方法前写[Obsolete ("Don‘t use Old Meltod"),ture]表示这是一个过时的方法,调用时会报错。
3、AttributeUsage
用于自定义的Attribute,其中可以带几个参数来规定这个自定义Attribute的适用范围等。
用户也可以自定义Attribute,自定义的Attribute继承于Attribute类,命名时自定义的Attribute名为自定义名加Attribute关键字组成,调用时可以使用完全名,也可以不写Attribute关键字,使用缩略的写法调用,下面是一个自定义Attribute的例子,定义了一个名为HelpAttribute的自定义Attribute,可以被应用于类,不可以在同一个单元重复使,不被继承:
[AttributeUsage(AttributeTargets.Class,AllowMultiple=flase,Inherited=flase)]
public class HelpAttribute:Attribute
{
protected string description;
public HelpAttribute(string description_in)
{
this.description=description_in;
}
public string Description
{
get
{
return this.description;
}
}
}
在调用这个自定义Attribute时,既可以使用HelpAttribute也可以使用Help,调用语句为[Help("this is a class")]。
在main函数中,可以通过反射或者这个类的Attribute携带的信息,获取方式如下:
HelpAttribute helpAttribute;
foreach(var attr in typeof(AnyClass).GetCustomAttribute(ture))
{
Console.WriteLine("AnyClass Description:{0}",helpAttribute.Description);
}
Console.ReadLine();
就能输出自定义的Attribute携带的信息。
八、反射机制
反射就是得到并解析这个类所携带的一些信息,便于程序自行观测并修改自己结构的行为。比如使用Type类创建实体可以获得一个变量的类型,Type是一个系统自带的抽象类,不能直接实例化,实例化时需要调用其他类的方法。使用反射获得方法名或其他信息时,可以在输入属性中加入BindingFlags标志位缩小查找范围。
九、预处理指令
预处理指令可以让C#编译器在编译开始前预处理一些信息,C#中预处理指令不能预定义宏函数,也不能给预处理指令赋值常量。常用的预处理指令有区域预处理指令#region、#endregion配对使用,使得两者之间的内容可以被折叠起来,只显示第一行的内容,使得代码简洁明了。条件预处理指令#define、#undef、#if、#else、#elif、#endif,在程序开始处使用#define命名一个条件变量,可以使用#undef是这个变量无效化,在程序中,使用#if和#endif包裹起来的部分就会被判断,#if条件符合#define规定的变量名就会运行#if后的代码,不符合则运行#else后的代码。#elif使用于#if中类似于条件语句的elseif。警告预处理指令#waring,可以用于程序任意位置,会在编译时强制抛出警告,#error类似于#waring用于强制抛出错误。行数规定指令#line,之后写一个整型变量,用于强制改变行数,或者写一个string变量,强制改变文件名,写default用于恢复行数显示。自定义预处理指令#pragma设定编译器状态或者指示编译器完成一些动作,比如#pragma message(“””)会在控制台输出里面的内容可以作为标识、#pragma warning改变错误警告的提示。
十、正则表达式
正则表达式是对字符串操作的一种公式,通常的操作有匹配、过滤、获取。正则表达式中会用到很多符号,以下是一个总结:
.所有字符\.匹配.
\d数字
\D非数字
\s空格
\S非空格
\w字符
\W非字符
\b边界
[]单个字符,里面的字符或关系
{}重复多次,填入两个逗号隔开的数字表示两个次数之间都可以匹配
*零个或者多个
+一个或者以上
?零个或者一个
^以……开始
$以……结尾
()显示位置
$&原字符串
等等。
C#的正则表达式都在Regex类下,通常使用有两种方法,一种是使用静态方法,一种是实例化Regex,同一正则表达式的Regex只能实例化一次。
匹配是否有相同内容使用的方法名是Match,需要填入两个参数作为对比。
替换使用的方法是Replace,共有三个参数,第一个是被修改的参数,第二个是匹配条件,第三个是修改的样式。
分割字符串使用Split,有两个参数,第一个是被分割的字符串,第二个是费根条件,得到的是一个分割后的数组。
十一、匿名函数
匿名函数是指一段函数或者表达式直接嵌套在程序中,没有专门的函数名。与委托区分就是委托是将一些已经命名的函数或者方法传递到程序中,匿名函数是把一个没有名称的方法或者函数传递到程序中。
使用委托实现一个方法的调入如下:
在main函数外需要写以下代码:
delegate void d(string s);
static void M(string s)
{
Console.WriteLine(s);
}
main函数中调用:
d test =new d(M);
在C#2.0版本中,提供了匿名函数的方法实现(Anonymous Method),在main函数外定义委托后,只需要在main函数中写以下代码:
d test =delegate(string s){Console.WriteLine(s);};
而在C#3.0版本中,更进一步的省略了写法(Lambda Expression):
d test =(s)=>{Console.WriteLine(s);};
以上方法的实现结果是一样的。
在没有参数列表时,Lambda列表不能实现委托的调用,只能使用匿名函数。
使用匿名函数的方法时在new的方法后(delegate(){})delegate中写参数列表,{ }中写具体的实现。匿名方法的参数不能使用ref,out等关键字修饰,也不能使用is关键字判断类型,匿名方法不能使用于不安全模式。
lambda表达式由三个部分组成,()=>{}左边是参数列表,中间是=>符号,右边是具体的实现。只有一个参数时,可以不写()
十二、LINQ
Linq:Lanuage Intergarted Query语言集成查询,是对数据查询或者操作的语句,弥补了面向对象的编程和数据库的间隙,没有Linq语句时,是无法对sql等类型的语句中的查询目标进行类型检查的,而使用Linq查询语句可以对不同的数据类型进行相似的处理,Linq语句对实现了IEnumerable接口的数据都能使用Linq语句查询。常用的Linq查询有Linq to sql、Linq to XML、Linq to DataSet、Linq to Objects。
Linq的实现方式有以下两种:
1、Query syntax
查询偶数的语法如下
var numQ=from num in numbers
where num%2 == 0
orderby num
select num;
就实现了查询numbers数组中的偶数并排序,调用结果时,使用foreach(var i in numQ)就可以调用查询结果。
Query语句一般使用三步走的方式实现,第一步创建数据源,就是创建要查询的源数据;第二步创建Query语句查询;第三步使用循环调用查询结果。
查询中常用的关键字有from(获取数据源)、in(源数据数组)、where(过滤条件)、orderby || group by(排序)、select(结果输出)、join(连接查询),使用循环调用结果时,调用查询语句名。再用到这个语句时,他才会被执行,如果要强制执行,可以调用查询结果下的方法。
where过滤条件中可以使用&&和||进行与或逻辑设定。
orderby排序默认为升序,可以使用descending降序排列,ascending表示升序。
group可以根据键值排序:from c in customers group c by c.city就是对c根据其城市属性排序。
join连接查询类似于sql的连接查询方式:from c in customers join e in emp on c.name equals e.name select new {name=c.name,id=e.id,city=c.city}就能查询到两个列表中具有相同name的数据,并按照new的定义输出信息。
into用于group排序后的数据,相当于对group查询到的数据进行打包,可以在where中使用打包的数据名进行一些其他的操作。
let相当于一个中间变量,对数据进行一步处理,产生一个新的变量名,后面的步骤就可以操作这个变量名。
举例let:
from s in strings
let w=s.ToUpper()
select w;
就将strings中的数据变成了大写。
2、Method syntax
var numQ=numbers.where(n=>n%2==0).OrderBy(n=>n);
调用方法实现查询,结果相同。
十三、扩展方法
ExtensionMethod扩展方法可以在不修改类的基础上,给类提供一些方法,例如Linq语句就可以为数组提供排序的方法,但是这个排序方法并不在数组的类型下。
自定义扩展方法必须是一个静态类下的静态方法,定义方法是:
public static class 类名
{
public static 返回值类型 方法名(this 被扩展的类名 参数名)
{
具体方法;
}
}
类名并不重要,不会影响实现,调用时,使用被扩展的类名的实例化对象调用方法名就可以实现调用。注意必须在方法名中使用this关键字标明被扩展的方法名,如果需要其他参数,可以在方法名的参数列表中this标出的变量名后用逗号隔开标明。
十四、初始化器
初始化器就是在一个类、对象或者集合初始化时候对其中的属性或者字段初始化值,初始化器分为对象的初始化器和集合的初始化器。
1、对象的初始化器
对于类StudentName定义如下:
public class StudentName
{
public string firstname{get;set;}
public string lastname{get;set;}
public int ID{get;set;}
public StudentName()
{
}
public StudentName(string firstname,string lastname)
{
firstname=firstname;
lastname=lastname;
}
}
对于这个类实例化时,使用不同的方式:
var StudentName1=new StudentName(“mi”,“wang”);
var StudentName2=new StudentName{firstname=“mi”,lastname=“wang”};
得到的实例化对象属性是一样的,而StudentName2使用的就是初始化器的方法。初始化器调用的是默认的构造函数,而StudentName1使用的是重载后的构造函数。使用初始化器,可以直接访问到可以被访问的类内的属性直接命名,而不用重写构造函数。使用构造函数和初始化器是可以一起使用的,如下:
var StudentName3=new StudentName(“mi”,“wang”){ID=20};
2、匿名类的初始化器
var pet=new {age=10,name=“mimi”};
就可以直接定义一个对象pet,具有属性年龄和名字,而不用专门的定义pet类,但是这个对象的属性是只读的,不能后续修改。
Linq语句的查询结果就可以使用匿名类重定义输出结果的数据结构。
3、集合的初始化器
在定义列表和键值对时,可以对其中的每个对象使用初始化器的定义方式定义其中的数据。