这是以前读《C#高级编程》做的笔记,不是很详细,主要是C#语言部分的主要知识点,翻出来看了看,有些知识点还是值得回忆的。分享给大家,C#的菜鸟还是可以借鉴的。也希望朋友们留下自己学习C#的经验和感受。
第一部分:C#语言部分
第三章:对象和类型
1.结构和对象
类是存储在堆上的引用类型,而结构是存储在堆栈中的值类型。
2.值类型和引用类型
引用类型的对象只包含对象的引用,它们只给方法传递这个引用,而不是对象的本身,所以对底层对象的修改保留下来。
值类型的对象包含的是实际的数据,所以传递给方法的是数据本身的副本。
比如:int通过值传递给方法,方法给int类型的值的任何改变都不会改变原int对象的值。但是数组或其他引用类型,方法就会使用该引用改变这个数组中的值。
3.ref参数
通过值传递变量是默认的,也可以迫使值参数通过引用传递给方法。要使用关键字ref,这样方法对变量所做的任何改变都会影响到原对象的值。
4.只读字段
其值不需要改变,但是在运行前其值是未知的。可以声明变量是只读的,readonly
5.结构体struct
有时只需要小的数据结构,类提供的功能多余我们所需要的功能时,通常使用结构—性能上可以提要。
结构和类的区别:
(1) 结构是值类型,不是引用类型。它们存放在堆栈中或是内联中。
(2) 结构不支持继承。
(3) 构造函数的工作方式有一些不同。
(4) 可以制定字段如何在内存中布局。
第四章:继承
C#不支持私有继承。
1.结构和类
结构总是派生于System.ValueType,还可以派生于任意多个接口。
类总是派生于用户选择的另一个类,还可以派生于任意多个接口。
2.虚方法
把一个基类函数声明为virtual,该函数就可以任意派生类中重写。
在 派生类的函数从写另一个函数时,要使用override关键字显式声明。
3.抽象类和抽象函数
把类和函数声明为abstract,抽象类不能实例化,而抽象函数没有执行代码,必须在非抽象的派生类中重写。
4.密封类和密封方法
在方法和类前声明为sealed。对于类来说,这表示不能继承该类;对于方法来说,这表示不能重写这个方法。
修饰符上面的还需理解。
5.接口
Public interface IDisposable
{
Void Dispose();
}
声明接口的语句于声明抽象类完全相同,但是不允许提供接口中的任何成员的执行方法。一般,接口只能包含方法,属性,索引器和事件的声明。
不能实例化接口,只能包含其成员的签名。接口成员总是公共的,不能声明为虚拟或静态。
接口的一个例子是C#中的foreach循环,实际上,foreach循环的内部工作方式就是查询对象,看看它是否实现system.Collections.IEnumerable接口。如果是,编译器就插入IL代码,使用这个接口上的方法替代集合中的成员。否则就会引发一个异常。
接口引用完全可以看做是类引用---但接口引用的强大之处在于,它可以引用任何实现该接口的类。
第五章:运算符和类型强制类型转换
1.Checked和unchecked运算符
如果变量的值超出了变量所能表示的范围,就会发生溢出。如果把一段代码段标记为checked,CLR就会执行溢出检查,发生溢出就会抛出异常。
unchecked则相反,不会抛出异常,但是会导致数据的丢失。默认情况下是unchecked的。
2.is运算符
可以检查对象是否与特定的类型兼容。
3.as运算符
用于执行引用类型的显式类型转换
4.sizeof运算符
可以确定堆栈中值类型的长度,只有在不安全的代码中使用sizeof运算符。
5.typeof运算符
返回一个特定类型的System.Type对象。在使用反射动态查找对象的信息时使用。
6.类型转换
隐式转换方式,显式转换方式
不能经行隐式转换的有:int—short,int---uint,uint---int,float---int.,任何数据类型转化成char。
Decimal转换为任何数字类型。
所有显式类型转换都可能是不安全的。最好截获异常。
7.装箱和拆箱
C#数据类型可以分为在堆栈上分配内存的值类型和在堆上分配内存的引用类型。
通过装想和拆箱可以把值类型转换为引用类型。或把引用类型转换成值类型,装箱用于吧一个值类型转换为引用类型object。
拆箱是个相反的过程,
8.运算符重载
运算符重载的关键是在类实例上不能总是只调用方法或属性,有时还需要做一些其他的工作,比如数值的运算等。
第六章:委托和事件
回调函数实际上是方法调用的指针,也称函数指针。委托的形式实现了函数指针的概念。
1. 委托:当把方法传递给其他方法时,需要使用委托。
C#中声明委托的方法和声明类相似,首先定义,然后实例化。
public delegate void Greeting (string name);
定义了一个委托Greeting,指定该委托的每一个实例都包含一个方法细节,该方法带有一个unit参数,返回void。
委托的一个特征是它们的类型是安全的,可以确保被调用的方法签名是正确的,但是他们不关心调用该方法的是什么类型的对象,甚至不考虑该方法是静态方法还是实例方法。
2. 事件:一般通知代码发生了什么事件,GUI主要处理事件。在发生事件时,运行库需要知道应该执行哪个方法。这就需要把处理事件的方法传送为委托的一个参数。
基于windows的应用程序也是基于消息的。用户单击窗体上的按钮,windows就会给按钮消息处理程序发送一个WM_MOUSECLICK消息,对于.NET来说,这就是按钮OnClick事件。
在开发基于对象的应用程序时,需要使用另一种对象通信方式,在对象中发生有趣的事情时,就需要通知其他对象发生了什么变化,这里要用到事件。可以把事件用作对象之间的通信介质。
委托就是用作应用程序接收到消息时封装事件的方式。
处理事件主要由事件接收器和事件发送器来完成。在事件接收器的某个地方有一个方法,它负责处理事件。在每次发生已注册的事件时,就执行这个事件处理程序。此时就需要使用委托了,由于无法设置两者之间的引用类型,而是使用委托作为中介。连接事件处理程序的过程称为封装事件。
封装click事件:btn.click+=new EventHandler(Button_Click)
意思是说引发btn的Click事件时,应执行Button_Click方法。EventHandler是事件用于把处理程序(Button_Click)赋予事件(Click)的委托。EventHandler委托已在.NET Framework中定义了。它位于System命名空间,所有在.NET Framework中定义的时间都使用它。
委托要求添加到委托列表中的所有方法都必须有相同的签名。
所以Button_Click方法的定义:
Private void Button_Click(Object sender,Eventargs e)
{
}
参数只能使用EventHandler委托,参数为object和EventArgs。
第一个参数是引发事件的对象,这取决于被单击的按钮。把一个引用发送给引用事件的对象,就可以把同一个的事件处理程序赋予对个对象。例如可以为几个按钮定义一个按钮单击处理程序,接着根据sender参数来确定单击的哪个。
第二个参数EventArgs是包含有关事件的其他有用的信息的对象。这个参数可以是任何类型,只要是派生于EventArgs即可。其命名模式是在类型的后面加上EventArgs。
方法的命名:“object_event.object”的命名约定。Object是引发事件的对象,event就是所引发的事件。
生成事件:(169页)
(1)创建事件和相应的委托。
Public delegate void ActionEventHandler(object sender,ActionCancel EventArgs ev);//定义一种新的委托类型。
Public static event ActionEventHandler Action;//定义事件
定义事件的语法要求指定与事件相关的委托,还可以使用在.NET中定义的委托。
(2)引发事件
ActionCancelEventArgs ev=new ActionCancelEventArgs()
Action(this,ev);
第七章:内存管理和指针(174页)
C#编程的一个优点是程序员不需要担心具体的内存管理,垃圾收集器会处理所有的内存清理工作。用户可以得到像C++语言那样的效率,而且不需要考虑在C++中那样内存管理的复杂性。
1.值类型的数据保存在堆栈中,而引用类型的数据保存在堆中。
2.垃圾收集:垃圾收集器运行时,会在堆中删除不再引用的所有对象,在完成删除后,堆会立即吧对象分散开来。与已经释放的内存混合在一起。
只要释放了能释放的所有对象,就会压缩其他对象,把它们都移动回堆的端部。垃圾回收器的这个压缩操作是托管的堆与未托管的堆的区别所在。
可以通过System.GC.Collect();调用。
3.释放未托管的资源
垃圾收集器不知道如何释放未托管的资源(文件句柄,网络连接和数据库连接)。对于未托管的资源,可以有两种方法进行资源的释放:
--声明一个析构函数。//不建议使用,因为立即收集器的存在,不能确定C#对象的析构函数何时执行。所以不能在析构函数中放某一时刻需要执行的代码。
--在类中实现System.IDisposable接口。//推荐使用
class Myclass:IDisposable
{
Public void Dispose()
{
}
}
Dispose()的执行代码显示释放由对象直接使用的所有未托管资源,并在所有实现IDisposable接口的封装对象上调用Dispose()。
一般情况下,最好的方法是执行这两种机制。
4.不安全的代码:
在C#中使用指针,不建议使用(184页),有待以后研究。
第八章:字符串和正则表达式(202页)
1.System.String类:专门用于存储字符串,允许对字符串进行许多操作。其他处理.NET类的字符串:System.Text,System.Text.RegularExpressions命名控件中的类。
3.格式化字符串:
一般使用String.Format()方法来获得该变量的合适字符串表示。
4.正在表达式:
正在表达式语言是专门用于字符串处理的语言,包含两个功能:
一组用于标识字符类型的转义代码;一个系统,在搜索操作中,它把字符串和中间结果的各个部分组合起来。
使用正则表达式,可以很好的对字符串执行复杂的操作,简化代码的编写量。实际上,是实例化了一个对象System.Text.RegularExpressions.Regex(甚至更简单:调用静态方法RegEx()方法),给它传送要处理的字符串和一个正则表达式,就可以了。
正在表达式中包含了转义序列和有特定含义的其他字符。
如:序列/b表示一个字的开头和结尾(字的边界)。
利用正则表达式检索字符:
String Text=”总字符串”;
String Pattern=”要检索的字符”;
MatchCollection Matches =Regex.Matches(Text,Pattern,RegexOptions.IgnoreCase//不区分大小写 | RegexOptions.ExplicitCapture);
Foreach(Match NextMatch in Matches)
{
Console.WriteLine(NextMatch.Index);
}
正则表达式的功能主要取决于模式字符。比如要查找以n开头的字,就可以定义string Pattern=@”"bn”。同样的,如果要查询以字母a开头,以序列ion结尾的所有字string Pattern=@”"ba"S*ion"b”;//转义序列"S表示任何不是空白的字符,*表示前面的字符可以重复任意次。
一些重要的特定字符或转义序列参看MSDN
第九章:集合(221页)
在.NET基类中,最简单的数据结构是数组,是System.Array类的一个实例。数组的缺点:在实例化时需要制定数组的大小,以后不能添加,插入或删除元素。
其他数据类型:集合,数组列表,栈,队列,有序列表,字典(映射)都在System.Collections命名空间中。
1. 集合:
是一组对象的合集,对象如果可以提供相关对象的引用,就是一个集合,称为枚举。使用foreach循环是集合的主要目的。
更专业的说法是集合必须实现接口System.Collections.IEnumerable。
Interface IEnumerable
{
IEnumerator GetEnumerator();//目的是返回枚举对象。
}
该枚举对象实现了接口System.Collections.IEnumerator。
Interface IEnumerator
{
object Current{get;}//Current属性返回一个对象引用。
bool MoveNext();//移动枚举,才能使它指向集合的第一个元素。
void Reset();//返回集合开头前面的位置。
}
2. 数组列表
类似于数组,但数组列表是可以增大的,System.Collections.ArrayList类来表示。这个特点与StringBulider类很相似。
实例化时,可以定义数组列表的大小,也可以不定义,默认情况下分配16个项大小,超出时加倍增长。如果定义了大小,则根据定义的大小成倍递加。
ArrayList a=new ArrayList(20);相应对象的方法参看MSDN。
与StringBuilder类不同的是,没有把数组列表转换为数组的方法。必须使用一个循环手工复制引用。---只能复制引用,不能复制对象。
3. Stack类
栈,适用于处理应用程序使用完后就删除的临时数据项。以后进先出的结构创建。(LIFO)
元素使用Push()方法放在栈中,而使用Pop()方法弹出栈外。
4. Queue类
队列,以先进先出(FIFO)的结构创建的集合。
元素使用Enqueue ()方法放入集合,用Dequeue()方法弹出集合。
5. SortedList类
SortedList类创建的集合,每一项都会指定一个用于引用该项的标识键。
SortedList name=new SortedList();
Name,Add(1,”brian”);//1就是标识键.
6. 字典和散列表
字典是复杂的数据结构,它允许按照某个键来访问元素,这个键可以是任意的数据类型。
它看起来像是一个数组,但不必用整数给它建立索引。当对象保存在数组中,但是需要用其他数据类型经行检索时,就可以用字典。
.NET中,基本的字典是由类Hashtable来表示的,微软提供了一个抽象类DirectionaryBase,可以派生出自己的字典类。
Hashtable name=new Hashtable(53);//初始化容量最好是个素数,这样工作效率最好。
第十章:泛型(245页)
1.泛型:类似C++的模板,可以生成强类型化的集合,出错的几率更小,提高性能。
优点:性能,类型安全,二进制代码重用。
List<int> list=new List<int>();
list.Add(44);//添加其他的类型会报错。
List<string> stringlist=new List< string >();
Stringlist.Add(“mystring”);
命名约定:用字母T作为前缀 public class List<T>{ }
2.泛型集合类:
在System.Collections.Generic命名空间中定义了许多泛型接口和集合类。可替换9章的集合类。
List<T>类:类似于ArrayList。不仅可以使用枚举添加和访问元素,还可以插入和删除元素,清空集合,把元素复制到数组中。还可以搜索和转换元素,使元素逆序。
Queue<T>类:类似于Queue类。队列的泛型版本。
LinkedList<T>类:没有非泛型集合的类似版本。是一个双向链表,其元素指向它前面和后面的元素。
3.创建定制的泛型类
类似于一般的类,只是要使用泛型类型声明。
public class MyGeneric<T>
{
Private T member;
Public void Method(T obj)
{
}
}
默认值:不能把null赋予泛型类型,因为泛型类型可以实例化值类型,而null只能用于引用类型。可以使用default关键字。
T doc = default(T);
泛型的类型可以用where来限制,where子句只能定义基类,接口和默认构造函数。不能定义,必须有泛型类型实现的运算符。
4.泛型方法
在泛型方法中,泛型类型用方法声明来定义。
Void Swap<T>(ref T x,ref T y)
{
}
把泛型类型赋予方法调用,就可以调用泛型方法:
Int i=4; int j=5;
Swap<T>(ref i,ref j);
5.泛型委托
6..NET框架的其他泛型类型
结构:Nullable<T>
数据库中的数据可以为空,但是C#的数字不能为空。一种解决方法是将数据库和XML文件中的数字映射为引用类型,引用类型可以为空。或者利用Nullable<T>,可以检查Nullable<T>的HasValue和Value属性。
委托:EventHandler<TEventArgs>
很多不同的事件处理程序定义了委托:
public sealed delegate void EventHandler(object sender,EventArgs e);
public sealed delegate void PaintEventHandler(object sender,PaintEventArgs e);
第一个参数sender,是时间的起源。第二个参数是一个泛型类型TEventArgs。Where子句指定TEventArgs的类型必须派生于基类EventArgs。
结构:ArraySegment<T>
表示数组的一段。如果需要数组的一部分时,就可以使用数组段。
int[] arr={1,2,3,4,5,6,7,8};
ArraySegment<int> segment=new ArraySegment<int>(arr,2,3)// 参数分别为数组名,偏移量,元素个数。偏移量为2,所以从第三个开始,个数为3,6是数组段的最后一个元素。
第十一章:反射(278页)-----迷糊
反射:描述了在运行过程中检查和处理程序元素的功能。反射允许完成一下任务:枚举类型的成员,实例化新对象,执行对象的成员,查找类型信息,查找程序集信息,检查应用于类型的定制特性,创建和编译新程序集。
1. 定制特性:
编写定制特性:
[FieldName(“SocialSecurityNumber”)]//属性有一个特性FieldName
public string SocialSecurityNumber
{
get {
………
}
编译为类库的方法:csc/target:library WhatNewAttributes.cs
编译为程序集的方法:csc/reference:System.Windows.Froms.dll TypeView.cs
2. 反射
(1) System.Type类:
在许多地方都会使用Type类,只存储引用类型:Type t=typeof(double)
获取指向给定类型的Type引用有3中常用方式:使用typeof运算符,使用GetType()方法,调用Type类的静态方法GetType()。
Type的属性:可以获取与类相关的各种名称的字符串,进一步获取Type对象的引用,许多Boolean属性表示这个类型是一个类还是一个枚举。
Type的方法:用于获取对应数据类型的成员信息:构造函数,属性,方法和事件等。
(2) Assembly类
它是在System.Reflection命名空间中定义的,允许访问给定程序集的元数据,它也包含可以加载和执行程序集的方法。
在使用Assembly类前,需要把相应的程序集加载到运行过程中,可以使用Assembly.Load(程序集名)或LoadFrom(程序集所在完整路径)。
查找在程序集中定义的类型:Assembly.GetTypes()方法。
查找定制特性:查找在程序集或类型中定义了什么定制特性的方法取决于该特性相关的对象类型。调用Assembly类的GetCustomAttributes()。
第十二章:错误和异常(297页)
.NET运行库会将每一个程序放到一个更大的try…catch里,捕获任意类型的异常。但是结果并不一定是你想要的。
1. 异常基类:
一般的异常类System.Exception派生于System.Object。通常不抛出System.Exception对象,因为它无法确定错误的本质。
两个重要的类,派生于System.Exception:System.SystemException,System.ApplicationException
2. 捕获异常:
一般把程序分成3种不同类型的代码块:try块,catch块,finally块。
进入try块,没错误就进入finally块,有错误就进catch块,然后进finally块。
302页有个很好的捕获异常的例子。
3. System.Exception属性:
HelpLink:链接到一个帮助文件上,可提供更多信息。Message:描述错误情况的文本。Source:导致异常的应用程序或对象名。StackTrace:堆栈上方法调用的信息。…….
4. try块可以嵌套执行:
try
{ try {……..}
Catch{…….}
}
Catch{……..}//.NET运行库顺序执行try块,查找何时的处理程序。
5. 用户定义的异常类:
第十三章:线程(317页)
1. 线程:是程序中独立的指令流。
2. 线程的处理:
是使用Thread类来处理的,该类在System.Threading命名空间中。一个Thread实例表示一个线程。
Thread depthchangeThread=new Thread(entryPoint);//实例化一个Thread对象
Thread构造函数需要一个参数,用于指定线程的入口—即线程开始执行的方法,需要使用委托,System.Threading类中定义好了,称为ThreadStart。传送给构造函数的参数必须是这种类型的委托。
Void changecolor() {……}
ThreadStart entryPoint=new ThreadStart(changecolor);//创建开启线程,参数是启动线程的方法
Thread depthchangeThread=new Thread(entryPoint);//创建线程
depthchangeThread.Name=”…”;//给线程起名
depthchangeThread.Start();//开启线程
启动了一个线程后,还可以挂起(Suspend()),恢复(Resume())或终止(Abort())它。
一般不直接使用Abort()来终止线程,使用异常机制可以更安全的终止线程。
3. 线程的优先级:
线程的优先级可以定义为ThreadPriority枚举的值。注意每个进程也有相应的优先级,给线程指定较高的优先级,可以确保它在该进程中比其他线程优先执行。
4. 同步:
使用线程的一个重要的方面是同步访问多个线程访问的任何变量。同步是指在某一时刻只有一个线程可以访问变量。
C#为同步访问提供简单方式:lock
lock(x)
{
DoSomthing();
}
Lock语句把变量放在圆括号中,以包装对象,称为独占锁或排它锁。当变量被包装在独占锁中时,其他线程就不能访问该变量。
控制变量的访问机制都可以通过.NET基类System.Threading.Monitor来控制。
不能滥用同步,因为它可能会降低性能。也可能造成死锁和竟态条件等问题。
死锁:两个线程都要访问被互锁的资源时发生。
竟态条件:当几个线程试图访问同一个数据,但没有充分考虑其他线程的执行情况时,就会发生竟态。它很少中断进程的执行,但是可能导致数据损坏。
5. 使用ThreadPool创建线程
通过Thread类,一次使用一个线程,来创建和删除线程。这种方式是很昂贵的(CPU密集型)。CLR包含一个内置的线程池,可以通过ThreadPool来访问。
ThreadPool有25个可用的线程。在确定使用ThreadPool类还是Thread类时,要考虑以下问题:要以最简单的方式创建和删除线程,使用的线程的性能要优先考虑时用ThreadPool类。
要控制所创建线程的优先级,使用的线程的寿命较长时,建议使用Thread类。