C#基础知识回顾(第二部分 共四部分)
第4章 面向对象的编程基础
4.1 类
类与对象:对象(实例):类的实例化 ;类:一组具有相同数据结构和相同操作的对象的集合
注意:要使用对象,必须先定义类,然后再创建对象
对象的生存周期:
对象在建立时分配了内存,创建对象实际上作了两个方面的工作
(1)使用new保留字要求系统分配内存;
(2)使用构造函数初始化数据
销毁对象也是做了两个方面的工作:
(1)释放占用的内存;
(2)将分配给对象的内存归还给堆(Heap)
4.1.1 类的组织
类的定义:定义对象具有的特征(字段、属性等)和可执行的操作(方法、事件等)
//自定义类的常用形式
[访问修饰符] [static] class 类名 [: 基类 [,接口序列]]
{
[类成员]
}
类成员:包括字段、属性、构造函数、方法、事件、运算符、索引器、析构函数等
public class Child
{
private int age;
private string name;
// 不带参数的构造函数
public Child()
{
name = "none";
}
// 带参数的构造函数
public Child(string name, int age)
{
this.name = name;
this.age = age;
}
// 输出方法
public void PrintChild()
{
Console.WriteLine("{0}, {1} years old.", name, age);
}
}
public class Program
{
public static void Main()
{
//使用new关键字创建对象,new后是调用的构造函数
Child child1 = new Child("Zhang San", 11);
Child child2 = new Child("Li Si", 10);
Child child3 = new Child();
// 显示结果
Console.Write("Child #1: ");
child1.PrintChild();
Console.Write("Child #2: ");
child2.PrintChild();
Console.Write("Child #3: ");
child3.PrintChild();
Console.ReadLine();
}
}
}
4.1.2 构造函数
作用:构造函数是一个特殊的方法,用于在建立对象时进行初始化的动作
好处:确保每一个对象在被使用之前都适当地进行了初始化的动作
构造函数特点:
1)每个类至少有一个构造函数。若程序代码中没有构造函数则系统会自动提供一个默认的构造函数。
2)一个构造函数总是和它的类名相同
3)构造函数不包含任何返回值
4)构造函数总是public的
一般在构造函数中作初始化工作,对于执行过程用时比较长的程序代码,最好不要放在构造函数中
默认构造函数:
(1)如果在类中不定义构造函数,系统会提供一个默认的构造函数。
(2)默认构造函数没有参数
(3)默认构造函数自动将非静态成员初始化为:
数值型:如int、double等,初始化为0
bool类型:初始化为false.
引用类型:初始化为null。
(4)如果自己定义了类的构造函数,则所有初始化工作由编程者自己完成
重载构造函数:
有时候可能会遇到这样的情况:在一个类中的多个方法中都要用到某一个数据成员,而该成员值必须从其他类中传递过来。这时,无参数的构造函数就不能胜任了,解决这个问题最好的办法就是:重载(Overloading)构造函数
using System;
using System.Collections.Generic;
using System.Text;
namespace OverloadingExample
{
class Program
{
public Program()
{
Console.WriteLine("null");
}
public Program(string str)
{
Console.WriteLine(str);
}
static void Main()
{
Program aa = new Program();
Program bb = new Program("How are you!");
Console.ReadLine();
}
}
}
4.1.3 字段和局部变量
当字段和局部变量名相同时,如果要引用静态字段,可以使用下面的形式:类名.字段名
如果是实例字段,则使用下面的形式:this.字段名 //this指当前实例
当然,如果没有出现字段和局部变量名重名的情况,引用字段的形式和引用局部变量的形式相同
4.1.4 访问修饰符
C#中有以下成员访问修饰符:
-
Public (常用)任何外部的类都可以不受限制的存取这个类的方法和数据成员
-
private (常用)类中的所有方法和数据成员只能在此类中使用,外部无法存取(默认)
-
Protected 除了让本身的类可以使用之外,任何继承自此类的子类都可以存取
-
Internal 在当前项目中都可以存取。该访问权限一般用于基于组件的开发,因为它可以使组件以私有方式工作,而该项目外的其它代码无法访问
-
Protected internal 只限于当前项目,或者从该项目的类继承的类才可以存取
-
Partial 局部类型,类的定义和实现可以分布在多个文件中,但都要使用partial标注,基类只需要声明一次,若多次声明则必须完全一致。
4.2 结构
结构(struct)是由一系列相关的、但类型不一定相同的变量组织在一起而构成的数据表示形式,所有结构类型都隐式地从类型object继承
public struct Point
{ public int x;
public int y;
public string s;
}
结构和类的区别与联系:
- 结构类型是值类型,类类型是引用类型
- 凡是定义为结构的,都可以用类来定义
- 创建轻量级对象时,可以使用结构
- 结构不能被派生
4.3 方法
方法(Method)是一组程序代码的集合,每个方法都有一个方法名,便于识别和让其他方法调用
4.3.1 方法的定义与使用
(1)方法必须放在某个类中
(2)定义方法的语法形式为:
访问修饰符 返回值类型 方法名称(参数序列)
{
语句序列
}
定义方法时,需要注意以下几点:
-
方法名不能和变量、常数或者任何声明在类中其它的成员相同
-
方法可以有参数,也可以没有参数,但是不论是否有参数,小括号都是必需的,如果参数序列中的参数有多个,则以逗号分开
-
结束某个方法的执行,可以使用return语句,程序遇到return语句后,会将执行流程交还给调用此方法的程序代码段。此外,还可以用return语句返回一个值
-
如果声明一个非void类型的方法,则方法中必须至少有一个return语句
4.3.2 方法中的参数传递
传递值类型的参数:
class Program
{ public static void AddOne(int a){a++;}
static void Main()
{
int a = 3;
Console.WriteLine("调用AddOne之前,a={0}", a);
AddOne(a);
Console.WriteLine("调用AddOne之后,a={0}", a);
Console.ReadLine();
}
}
传递引用类型的参数:
引用类型参数的格式:ref 参数类型 参数
class Program
{ public static void AddOne(ref int a){a++;}
static void Main()
{
int a = 3;
Console.WriteLine("调用AddOne之前,a={0}", a);
AddOne(ref a);
Console.WriteLine("调用AddOne之后,a={0}", a);
Console.ReadLine();
}
}
输出多个引用类型的参数:
输出引用类型参数的格式为: out 参数类型 参数名
class Program
{
public static void MyMethod(out int a, out int b)
{ a = 5;
b = 6;
}
static void Main()
{ int x, y;
MyMethod(out x, out y);
Console.WriteLine("调用MyMethod之后,x={0},y={1}", x, y);
Console.ReadLine();
}
}
输出类型参数与引用类型参数区别:
从CLR的角度看,关键字out和关键字ref是等效的,这就是说,无论使用哪个关键字,都会生成相同的元数据和IL代码
但是,C#编译器将两个关键字区别对待,在C#中,这两个关键字的区别在于哪个方法负责初始化引用对象
- 如果方法的参数标记为out,那么调用者不希望在调用方法之前初始化对象,被调用的方法不能读取对象的值,而且被调用的方法必须在返回之前为对象赋值
- 如果方法的参数标记为ref,那么调用者必须在调用方法之前首先初始化参数的值,被调用的方法可以读取参数或为参数赋值
传递个数不确定的参数:
需要传递的参数个数不确定时,可以采用params关键字,其后一般跟一个数组
class Program
{
public static float Average(params long[] v)
{
long total, i;
for (i = 0, total = 0; i < v.Length; ++i)
total += v[i];
return (float)total / v.Length;
}
static void Main()
{
float x = Average(1, 2, 3, 5);
Console.WriteLine("1、2、3、5的平均值为{0}", x);
x = Average(4, 5, 6, 7, 8);
Console.WriteLine("4、5、6、7、8的平均值为{0}", x);
Console.ReadLine();
}
}
4.3.3 方法重载
方法重载是指具有相同的方法名,但参数类型或参数个数不完全相同的多个方法可以同时出现在一个类中
using System;
class Program
{
public static int Add(int i, int j)
{return i + j;}
public static string Add(string s1, string s2)
{return s1 + s2;}
public static long Add(long x)
{return x + 5;}
static void Main()
{
Console.WriteLine(Add(1, 2));
Console.WriteLine(Add("1", "2"));
Console.WriteLine(Add(10));
//按回车键结束
Console.ReadLine();
}
}
4.4 属性与索引器
4.4.1 属性
属性是一个可以访问对象或类的特征的成员,和字段类似,属性有如下特征
1.它是命名的类成员;2.它是有类型;3.它可以被赋值和读取
与字段不同,属性是一个函数成员:1.它不为数据存储分配内存;2.它执行代码
属性指的是一组两个匹配的、称为访问器的方法:1.set修改器用来属性赋值;2.get访问器用来获取属性值
属性的作用:可以限制外部对类中成员变量的存取权限
属性的声明与定义:
int Myvalue{
set {CodeToSetPropertyValue}
get{
CodeToGetPropertyValue
return SomeInt;
}
}
set修改器:用于设置数据成员的值,一个单独的、隐式的值参,名称为value,与属性的类型相同,一个返回类型void
get访问器:用于获取数据成员的值,一个与属性类型相同的返回类型
public class MyClass
{
private int number = 0;
public int MyNumber
{ get{return number;}
set{if (value > 0) {
number = value;} }
}}
只读和只写属性:
只有get访问器的属性称为只读属性。只读属性是一种安全的方法,把一项数据从类或类的实例中传出,而不允许太多的访问
只有set访问器的属性称为只写属性。只写属性是把一项数据从类的外部传入类而不允许太多访问的安全方法
this关键字:
this关键字在类中使用,是对当前实例的引用。可用在以下情况:
实例构造函数;实例方法;属性和索引的实例访问器;用于区分类的成员和本地变量或参数;作为调用方法的实参
4.4.2 索引器
索引器是一组get和set访问器,类似于属性的访问器,用于封装内部集合或数组
string this [int index]
{
set{ SetAccessorCode }
get{ GetAccessorCode }
}
索引器和属性在很多方面相似:
1.和属性一样,索引器不用分配内存来存储;2.索引器和属性都主要被用来访问其他数据成员,这些成员和它们关联,它们为这些成员提供设置和获取访问
索引器与属性的不同:
属性通常访问单独的数据成员,索引器通常访问多个数据成员
索引器的特点:1.索引器用于封装内部集合或数组;2.索引器在语法上方便了程序员将类、结构或接口作为数组进行访问;3.要声明类或结构上的索引器,需要使用this关键字
public int this[int index] // 索引器声明
{// get and set accessors}
第5章 面向对象的高级编程
5.1 类的继承性与多态性
封装:隐藏调用者不需要了解的信息(怎样隐藏一个对象的内部实现的?)
继承:简化类的设计(怎样促进代码的重用的?)
多态:类为名称相同的方法提供不同实现方式的能力(怎样用同样的方式处理相关对象的?)
封装:对象封装了对象的数据以及对这些数据的操作,对象是属性和操作的集合,对象的表现(服务、接口)和实现细节分开
封装就是定义一个类,然后给该类的属性(成员变量)和方法加上访问控制修饰词(public,private,protected,默认包访问权限)
5.1.1 基类和扩充类
继承:继承表示现实世界中遗传关系的直接模拟,它表示类之间的内在联系以及对属性和操作的共享
扩充类的注意点:扩充类不能删除它所继承的任何成员
扩充类组成成员:1.自己声明的成员;2.基类的成员
两种实现继承的方式:类继承和接口继承
类继承只允许单继承,接口继承可实现多重继承
被继承的类叫做基类,继承自其他类的类叫做扩充类
声明扩充类的语法:
[访问修饰符] class 扩充类名称:基类名称
{
//程序代码
}
扩充类继承了所有定义在基类中数据的定义和方法(针对Public成员和Protected成员),但是扩充类不继承基类的构造函数
5.1.2 多态性
多态性是指对象可以表示多个类型的能力,在C#中,多态性的定义是:同一操作作用于不同的类的实例,不同的类将进行不同的解释,最后产生不同的执行结果
三种实现多态性的方式:1.通过继承实现多态性;2.通过抽象类实现多态性;3.通过接口实现多态性
多态性通过派生类覆写基类中的虚函数型方法来实现,所有的面向对象语言都具有多态性
虚拟方法:在基类中,如果想让某个方法或者事件被扩充类重写,可以使用修饰符virtual表明
public virtual void myMethod()
{
……//程序代码
}
//扩充类则用·override重写
public override void myMethod()
{
……//程序代码
}
使用虚拟方法和重写方法时,要注意下面几个方面:
- 虚拟方法不能声明为静态的
- Virtual不能和private一起使用
- 重写方法的名称、参数个数、类型以及返回值都必须和虚拟方法的一致
隐藏基类的方法:
使用new关键字
在扩充类中,可以使用new关键字来隐藏基类的方法,即使用一个完全不同的方法取代旧的方法,与方法重写不同的是,使用new关键字时并不要求基类中的方法声明为virtual,只要在扩充类的方法前声明为new,就可以隐藏基类的方法
在扩充类直接调用基类的方法:
使用base关键字
静态多态(方法的重载)与动态多态(虚拟方法的重写):
(1)静态多态 也称为编译时的多态 是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作
(2)动态多态 也称为运行时的多态 是指直到系统运行时,才根据实际情况决定实现何种操作。C#中运行时的多态性是通过覆写虚成员实现(覆盖即派生类通过override关键字重写基类的某个虚拟方法)
5.1.3 抽象类
使用abstract修饰符,抽象类只能做基类,其与非抽象类的区别:
-
抽象类不能直接被实例化,只能在扩充类中通过继承使用,对抽象类使用new运算符会产生编译时错误
-
抽象类可以包含抽象成员,而非抽象类不能包含抽象成员。当从抽象类派生非抽象类时,这些非抽象类必须具体实现所继承的所有抽象成员
5.1.4 密封类
密封类是指不能被其他类继承的类,在C#语言中,使用sealed关键字声明密封类,sealed关键字也可以限制基类中的方法,防止被扩充类重写,带有sealed修饰符的方法称为密封方法。密封方法同样不能被扩充类中的方法继承,也不能被隐藏
5.1.5 继承过程中构造函数的处理
C#在内部按照下列顺序处理构造函数:(向上寻找值顶级基类,再从基类依次向下执行构造函数)
从扩充类依次向上寻找其基类,直到找到最初的基类,然后开始执行最初的基类的构造函数,再依次向下执行扩充类的构造函数,直至执行完最终的扩充类的构造函数为止
注意:对于无参数的构造函数,执行不会出现问题。如果是带参数的构造函数,还需要做相应的处理
5.2 版本控制
用C#编写方法时,如果在扩充类中重写基类的方法,需要用override声明;要隐藏基类的方法,需要用new声明,这就是C#进行版本控制的依据
-
调用非虚拟方法时不会受到版本的影响
-
调用虚拟方法的实现部分可能会因扩充类的重写而影响执行结果
使用virtual、new与override进行版本控制
5.3 接口
接口简介:接口就是语义相关的一个或多个抽象成员组成的命名集合
接口的特点:只有声明部分,没有实现部分,接口成员的实现是通过类完成的,定义在接口中的方法都是public的
接口的声明:使用interface关键字声明一个接口
[访问修饰符] interface 接口名称
{
// 接口体
}
接口声明只能包含如下类型的成员的声明:方法,属性,事件,索引
这些成员的声明不能包含任何实现代码,而在每一个成员声明的主体后必须使用分号
接口与继承:接口可以继承接口,不同的类可以实现同一个接口,实现具有重复成员的接口,且可以实现多个接口(与单继承相比较,即类的多继承可以通过接口实现)
显示方式实现接口:显示实现接口时,不能通过类的实例进行访问,而必须使用接口实例,作用是调用者只能通过接口调用而不是底层的类来访问,可以达到实现同一个类实现不同接口中,方法名相同的问题
5.4 泛型
5.4.1 泛型简介
泛型:比较广泛的数据类型,是数据类型的一种通用的表示形式
通过泛型可以定义类型安全的数据结构,而无须使用实际的数据类型。这能够显著提高性能并得到更高质量的代码,因为您可以重用数据处理算法,而无须复制类型特定的代码。在概念上,泛型类似于 C++ 模板,但是在实现和功能方面存在明显差异
5.4.2 C#中的泛型
泛型(Generic)是具有占位符(类型参数)的类、结构、接口和方法,它与普通类的区别是泛型多了一个或多个表示类型的占位符,这些占位符用尖括号括起来
class Mystack<T>
{
int stackpointer=0;
T[] stackarray;
public void push(T x)
{…}
public T pop()
{…}
}
Mystack<int> my1 = new Mystack<int>();
Mystack<float> my2 = new Mystack<float>();
参数类型的约束:由于使用泛型,因此不能确定数据的类型,同时运算也收到限制
class Example<T>
{
public bool LessThan(T i, T j)
{
if(i>j){return true;} return false; }
}
where子句:where TypeParam : constraint, constraint,…,其中TypeParam为类型参数, constraint为约束列表
where子句的要点:1.它们在类型参数列表的关闭尖括号之后列出;2.它们不使用逗号或其它符号分隔;3.可以以任何次序列出
class MyClass<T1, T2, T3>
where T2:Customer
where T3:IComparable
//T1未绑定,不受约束
仅限于Customer类或继承Customer类的类型才能作为T2的实参,只有实现IComparable接口的类型才能作为T3的实参(一般在接口名称前加I表示这是一个接口)
泛型结构:
struct myStruct<T>
{
public T data;
}
static void Main()
{
myStruct<string> str = new myStruct<string>();
str.data = "string";
Console.WriteLine(str.data);
Console.ReadLine();
}
泛型方法:泛型方法可以在泛型和非泛型类以及结构、接口中声明
//声明泛型方法
public void myFun<T,S>(S str){}
//<T,S>为类型参数列表,(S str)为方法参数列表
public void fun<T>(T[] arr)
{
Array.Reverse(arr);
foreach(T item in arr)
Console.Write(item.ToString());
}
…
int[] intArray = new int[] {3,4,6,9};
string[] strArray = new string[]{“a”,”b”,”c”};
fun<int>(intArray);fun(intArray);
fun<string>(strArray);fun(strArray);
可空泛型:可空类型是**泛型结构Nullable**的实例
Nullable读作“可以为null的Int32”,可以将其赋值为任一个32位整数值,也可以将其赋值为null值
可空泛型的特点:(1)可空类型表示可被赋值为null值的值类型变量;(2)语法“T?”是泛型“Nullable”的简写,此处的T为值类型。这两种形式可以互换,如Nullable也可以写为int?;(3)程序员可以使用泛型的HasValue和Value只读属性测试是否为空和检索值,如果此变量包含值,则HasValue属性返回True;如果此变量的值为空,则返回False
public void myFun(Nullable<int> b)
{
Console.WriteLine(b);
}
static void Main()
{
Program p = new Program();
p.myFun(null);
p.myFun(10);
Console.ReadLine();
}
泛型集合:在System.Collections.Generic命名空间下,提供了常用的泛型集合类
泛型集合类 | 非泛型集合类 | 泛型集合用法举例 |
---|---|---|
List | ArrayList | List dinosaurs = new List( ); |
Dictionary<TKey,Tvalue> | Hashtable | Dictionary<string, string> d = new Dictionary<string, string>( );d.Add (“txt”, “notepad.exe”); |
Queue | Queue | Queue q = new Queue( );q.Enqueue(“one”); |
Stack | Stack | Stack s = new Stack( );s.Push(“one”);s.Pop( ); |
SortedList<TKey,TValue> | SortedList | SortedList<string, string> list = new SortedList<string, string>( );list.Add (“txt”, “notepad.exe”); list.TryGetValue(“tif”, out value)) |
5.5 泛型集合类的知识补充
5.5.1 列表和排序列表
List泛型类表示可通过索引访问的强类型对象列表,该类提供了对列表进行搜索、排序和操作的方法:
- Add方法:将指定值的元素添加到列表中
- Insert方法:在列表的中间插入一个新元素
- Contains方法:测试该列表中是否存在某个元素
- Remove方法:从列表中移除带有指定键的元素
- Clear方法:移除列表中的所有元素
SortedList排序列表,用法与List类似
5.5.2 链表
LinkedList为双向链表,LinkedList对象中的每个节点都属于LinkedListNode类型。每个节点又指向其Next节点和Previous节点
LinkedList接受 null引用作为引用类型的有效Value属性,并且允许重复值
string[] words = { "a", "b", "c" };
LinkedList<string> sentence = new LinkedList<string>(words);
sentence.AddFirst("dog"); //结果为"dog","a","b","c",
5.5.3 字典和排序字典
Dictionary<TKey, TValue>泛型类提供了从一组键到一组值的映射,字典中的每个添加项都由一个值及其相关联的键组成,通过键来检索值,该泛型类提供的常用方法如下
- Add方法:将带有指定键和值的元素添加到字典中
- TryGetValue方法:获取与指定的键相关联的值
- ContainsKey方法:确定字典中是否包含指定的键
- Remove方法:从字典中移除带有指定键的元素
注意:一个字典中不能有重复的键
SortedDictionary<TKey, TValue>泛型类用法与字典的用法相同,区别在于其保存按键进行升序排序后的结果
5.5.4 队列
Queue泛型类表示对象的先进先出集合,队列在按接收顺序存储消息方面非常有用,存储在队列中的对象在一端插入,从另一端移除
队列可以保存null值并且允许有重复的元素,常用方法如下:
- Enqueue方法:将指定元素插入列尾
- Dequeue方法:队列首元素出列
Queue<string> numbers = new Queue<string>( );
numbers.Enqueue("one");
numbers.Enqueue("two");
numbers.Dequeue( );//移除第一个元素
5.5.5 堆栈
Stack泛型类表示同类型实例的大小可变的后进先出(LIFO)集合
堆栈可以保存null值并且允许有重复值,常用方法如下:
- Push方法:将指定元素插入栈顶
- Pop方法:弹出栈顶元素
Stack<string> numbers = new Stack<string>( );
numbers.Push("one");
numbers.Push("two");
numbers.Pop( );
5.5.6 哈希集合
HashSet泛型类提供了高性能的数学集合运算,一个HashSet对象的容量是指该对象可以容纳的元素个数,常用方法:
- Add方法:如果元素不在集合中,添加并返回元素是否在集合中
- Remove方法:删除集合中指定的元素
- Clear方法:删除集合中的所有元素
- UnionWith方法:并集或Set加法
- IntersectWith方法:交集
- ExceptWith方法:set减法
- SymmetricExceptWith方法:余集
5.6 反射
- 反射的用途是在程序或装配件中查找有关类型的信息,或者从装配件中读取元数据
反射包含的大多数类都在System.Reflection命名空间中,常用的类:
- Type类:查找有关类型的相关信息
获取指向给定类型的Type对象有2种常用方式:(1)使用C#提供的typeof关键字获取指定类型的Type对象,例如:Type t = typeof(double);(2)调用Type类的GetType静态方法,例如:Type t = Type.GetType(“System.Double”)
- Assembly类:在System.Reflection命名空间中定义,它允许程序员访问给定装配件的元数据
- 根据类型动态创建对象,获取方法以及动态调用方法
- System.Activator提供了方法来根据类型动态创建对象
5.7 序列化与反序列化
- 序列化
获取对象并将其状态信息转换为可存储或可传输形式的过程
- 反序列化
将对象还原回序列化之前的内容
序列化和反序列化是一个互逆的过程
- .NET Framework提供的两种序列化技术
- 二进制序列化:可以保持类型不变,即可以在应用程序的不同调用之间保留对象的状态
- XML和SOAP序列化:仅序列化公共属性和字段,不保存类型
如何序列化一个类:在类的上方加上Serializable特性即可
[Serializable]
public class MyClass{}
二进制序列化:
MyClass user = new MyClass();
……
IFormatter formater = new BinaryFormatter();
Stream stream = new FileStream("UserInfo.bin", FileMode.Create, FileAccess.Write, FileShare.None);
formater.Serialize(stream, user);
stream.Close();
特点:二进制序列化会将一个类的所有成员变量都进行序列化,包括私有变量、公共属性、方法等
二进制反序列化:
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream("UserInfo.bin",FileMode.Open,FileAccess.Read,FileShare.Read);
MyClass c = (MyClass)formatter.Deserialize(stream);
stream.Close();
特点:反序列化后,对象c与序列化之前的状态完全相同
XML序列化与反序列化:
(1)XML 序列化仅将对象的公共字段和属性值序列化为XML流,而不转换方法、索引器、私有字段或只读属性(只读集合除外)
(2)XML 序列化不包括类型信息,即不能保证序列化后的对象在被反序列化时,变为同一类型的对象。
(3)XML序列化的实现步骤:
Class1 user = new Class1();
user.AccountName = "aa";
XmlSerializer mySerializer = new XmlSerializer(typeof(Class1));
StreamWriter myWriter = new StreamWriter("UserInfo.xml");
mySerializer.Serialize(myWriter, user);
myWriter.Close();
(4)XML反序列化的步骤
Class1 c;
XmlSerializer mySerializer = new XmlSerializer(typeof(Class1));
FileStream myFileStream =
new FileStream("UserInfo.xml", FileMode.Open);
c = (Class1) mySerializer.Deserialize(myFileStream);
myFileStream.Close();
注意:反序列化一个对象时不会调用构造函数,这一点与创建对象不同
5.8 委托和事件
5.8.1 委托
委托是一个类型安全的对象,它指向程序中另一个以后会被调用的方法(或多个方法),委托类型包含3个重要的信息:
1.它所调用的方法的名称;2.该方法的参数(可选);3.该方法的返回值(可选);
委托的最大特点是:1.它不知道或不关心自己引用的对象的类;2.既可以指向静态方法,也可以指向实例方法;3.委托的应用:回调(CallBack)机制、事件处理;4.使用委托使程序员可以将方法封装在委托对象内,然后可以将该委托对象传递给可调用所引用方法的代码,而不必在编译时知道将调用哪个方法
委托类似于C/C++中的函数指针,它能够引用函数,通过传递地址的机制完成,委托是一个类,当你对它实例化时,要提供一个引用函数,将其作为它构造函数的参数
每一个委托都有自己的签名:
Delegate int SomeDelegate(string s, bool b);
这是是一个委托声明,这里的签名就是SomeDelegate这个委托有string和bool类型的形参,返回一个int类型
当对委托实例化时,要提供一个引用函数,将其作为它构造函数的参数
注意:被引用的这个函数必须和委托有相同的签名,即:参数类型、参数个数以及返回值类型相同
private int SomeFunction(string str, bool bln){…}
可以把这个函数传给SomeDelegate的构造函数,SomeDelegate sd = new SomeDelegate(SomeFunction);,sd引用了SomeFunction,如果调用sd,SomeFunction也会被调用
5.8.2 事件
事件是类在发生其关注的事情时用来提供通知的一种方式,事件的发生一般都涉及2个角色:事件发行者和事件订阅者
- 事件发行者(Publisher):一个事件的发行者,也称作是发送者(sender),其实就是个对象,这个对象会自行维护本身的状态信息,当本身状态信息变动时,便触发一个事件,并通知所有的事件订阅者
- 事件订阅者(Subscriber):对事件感兴趣的对象,也称为Receiver,可以注册感兴趣的事件,在事件发行者触发一个事件后,会自动执行这段代码
事件的重要事项:
1.触发事件:调用或触发事件。当事件被触发时,所有注册到它的方法都会被依次调用
2.发布者:让事件被其他类或者结构可见并使用的类或结构
3.订阅者:把事件和发布者关联注册的类或结构
4.事件处理程序:注册到事件的方法。可以在事件所在的类或结构中,或者在不同的类或结构中
发布者定义了事件成员
订阅者注册在事件成员被触发时要调用的方法
当发布者触发事件时,所有列表中的事件处理程序都会被调用
事件是响应用户对鼠标、键盘操作或自动执行某个与事件关联的方法的行为。事件和方法一样具有签名,签名包括名称和参数列表
- 事件的签名通过委托类型来定义,为事件定义委托的常用形式为:
public delegate MyEventHandler(Object sender, EventArgs e)
- 事件可以使用event关键字进行声明
public event MyEventHandler TriggerIt;
- 若要引发该事件,可以定义引发该事件时要调用的方法
public void Trigger( ){TriggerIt( );}
- 可以通过“+=”和“-=”运算符向事件添加委托来订阅或取消对应的事件
myEvent.TriggerIt += new MyEventHandler(myEvent.MyMethod);
myEvent.TriggerIt -= new MyEventHandler(myEvent.MyMethod);
事件有委托:事件提供了对委托的结构化访问,事件被触发时,它调用委托来依次调用列表中的方法
匿名委托:使用匿名方法,不必创建单独的方法,因此减少了实例化委托所需的编码系统开销
如果文章对您有所帮助,记得一键三连支持一下哦~