类作为面向对象特别是创建窗体程序的基础还需要包含一些其他的特征
一、事件
事件是一种使对象或类能够提供通知的成员。客户端可以通过提供事件处理程序为相应的事件添加可执行代码 事件有很多地方类似与委托他就像一个针对特殊用途的委托 注册到事件上的方法会在事件发生时被调用
1.1.与事件有关的事项:1.触发事件 调用或触发事件当事件触发时所有注册到它的方法都会被依次调用 2.发布者 让事件被其他类或者结构可见并使用的类或者结构(定义事件的类) 2.订阅者 把事件和发布者关联注册的类或者结构 3.事件处理程序 注册到事件的方法可以在事件所在的类或者结构中或者在不同的类或者结构中
事件包含了一个私有委托或者说他将一个委托封装起来(类似与属性与字段 字段相当于委托将他们都私有化起来 而属性相当于事件将他们公布出来供认使用)
所以事件提供了对它的私有控制委托的结构化访问
委托可以有赋值语句(=)而对于事件我们只可以添加(+=)删除(-=)或者调用事件处理程序(即事件将委托标准化了只有一种形式看起来没有委托那么不规范)
相同事件被触发时它其实是调用委托来依次调用调用列表中的方法
1.2.需要在事件中使用的代码:1.委托类型声明 事件和事件处理程序必须有共同的签名和返回类型,他们通过委托类型声明进行描述 2.事件处理程序声明 这些在订阅者类的方法中的描述会在事件触发时被执行 3.事件声明 这个事件发布者类中的声明保存并调用事件处理程序 4.事件注册 这段代码把事件连接到事件处理程序 5.触发事件的代码 发布者类中的这段代码调用事件导致它调用事件处理程序
1.3.事件是成员:事件不是一种类型这就决定不能用new表达式来创建他的对象并且事件必须声明在类或者结构中和其他成员一样 事件成员被隐式自动初始化为null
1.4.事件声明 <修饰符> event 类型 标识符{<事件访问器声明>}
事件修饰符除了访问修饰符还有new,static,virtual,sealed,override,abstract,extern
事件声明的类型必须是委托类型而该委托类型必须至少具有与事件本身一样的可访问性
事件访问器声明可以没有。事件只允许+=和-=运算符而事件访问器是专门用来定义这练个运算符的
public event EventHandler Elapsed{add{//执行+=运算符的代码}remove{执行-=运算符的代码}}//类似于属性事件访问器中也有个叫做value的隐式值参数但不同于类型事件访问器表现为void方法不能使用return语句
using System; public class Child//事件发布者 { public int score { get; set; } public delegate void ConDel(int temp);//委托声明 public ConDel del; public event ConDel RePuEvent;//事件声明 //从两种方法上看事件相当于定义个一个委托的变量(委托是一个类而事件是一个成员) //他相当于将一个委托的成员私有化用事件来对外访问委托 //而且事件提供了一种标准的注册方法委托有= += -=等一系列不规则的操作符 //事件还可以定义其访问控制器用于+=和-=的操作甚至可以不让用其中一个 public void exam()//事件处理程序 由于事件是类的成员不用像委托那样需要代入变量 { if (score >= 0&&score <= 100) { if (RePuEvent != null)//触发事件前都要检测事件是否为空即是否有人注册事件 RePuEvent(score);//触发事件 事件与委托一样也是指向一种签名的方法 } } public void delExam() { if (score >= 0 && score <= 100) { if (del != null) del(score); } } } public class Father//订阅者1 { public void RePu(int score) { if (score >= 85) { Console.WriteLine("考了{0}分不错,给你100花花",score); } else if (score >= 60) { Console.WriteLine("考了{0}分刚及格,这个月没零花钱了", score); } else { Console.WriteLine("考了{0}分嗯,我又有换皮带的理由了", score); } } } public class Mother//订阅者2 { public static void RePu(int score) { if (score >= 85) { Console.WriteLine("考了{0}分不错,你那天说的衣服买了", score); } else if (score >= 60) { Console.WriteLine("考了{0}分刚及格,一个月没肉吃", score); } else { Console.WriteLine("考了{0}分嗯,你爸已经说了好久要换皮带了", score); } } } public class Example { public static void Main(string[] args) { Child mine = new Child(); Father myFather = new Father(); mine.score = 60; { mine.RePuEvent += myFather.RePu; //用事件实现了看管 mine.RePuEvent += Mother.RePu; //非常的规范只有+=这种注册事件的操作 mine.exam(); } { mine.del = myFather.RePu;//有=又有+=并且还定义委托很不规范而且 mine.del += Mother.RePu; mine.delExam(); } Console.ReadKey(); } }
·事件定义本身就是创建了一个私有的委托变量而用事件来公开对委托的注册或取消(类似与将字段设问私有而提供一个属来操作字段)
·事件和委托相同都是一个能指向多个方法的指针 但事件是一个成员调用他就相当于调用方法一样 而委托是一个类型想调用委托必须先声明一个委托变量才行(可以理解为事件与委托的变量的概念相同)
·事件用+=和-=来注册和取消方法调用事件会按顺序执行注册入事件的方法(如果方法有参数(所有方法的参数列表都必须是相同的这与委托一样)这事件和方法一样可以后跟括号来向方法代入参数) 这里注意触发事件即让事件执行前必须先检测事件是否为null
二、索引器
索引器允许对象能够像数组相同的方式进行数据处理
索引也有一组get和set访问器类似与属性(可以把索引想象成提供获取和设置类的多个数据成员的属性通过提供索引在许多可能的数据成员中进行选择)
当然和属性一样索引可以只有一个访问器(比如string类中的索引只有get访问器所以string对象是只读的)也可以两者都有(类似与数组)
索引总是实例成员(即不能被声明为static)
和属性一样实现get和set访问器的代码不必一定要关联到某个字段或属性,这段代码可以做任何事情或者什么都不做只要get访问器返回指定类型的值即可
2.1.声明索引 <修饰符> 类型 this [形参表] {get;set;(访问器声明)}
修饰符可以是除了访问修饰符和new,virtual,sealed,overrife,abstract,extern(索引器总是实例成员所以不能有static关键字)
类型 this [形参表] 索引器不具有自定义的名称而是用定义的实例名访问类型是引入索引器的元素类型(即传入传出类型) 形参表是索引器的索引可以有多个(类似与一维数组和多维数组的表示方法)注意形参表中至少有一个参数
访问器 当索引被调用于赋值时 set访问器被调用并接受两项数据1.一个隐式参数value它持有要保存的数据2.一个或更多索引参数表示数据应该保存到哪里 get访问器它返回一个与索引其类型相同的值(即有return语句) 注意无论是set还是get方法体内的代码必须检查索引参数确保它表示的是那个字段并返回该字段的值(类似数组的越界检测)
索引器可以重载
1 using System; 2 3 public class Employee 4 { 5 string lastName;//两个私有变量 6 string firstName; 7 public Employee(string lastName, string firstName)//构造函数 8 { 9 this.firstName = firstName; 10 this.lastName = lastName; 11 } 12 public string this[int index]//索引器用他来像数组一样对成员变量赋值 13 //如果类中多个成员变量有一定的关系时可以用索引器来实现访问而不是给各个成员变量设置属性 14 { 15 get 16 { 17 switch (index)//switch语句 18 { 19 case 0: return lastName;//没有break语句因为遇到return会退出函数不需要break 20 case 1: return firstName; 21 default: throw new ArgumentOutOfRangeException("index"); 22 //索引判断超出索引值抛出异常 23 } 24 } 25 set 26 { 27 switch (index) 28 { 29 case 0: this[0] = value; break; //value为传入的值 这里需要break语句 30 case 1: this[1] = value; break; 31 default: throw new ArgumentOutOfRangeException("index"); 32 } 33 } 34 } 35 public string this[int index1, int index2]//重载索引器接收两个变量 36 { 37 get 38 { 39 if (index1 == 0 && index2 == 1) 40 return lastName + firstName; 41 else 42 throw new ArgumentOutOfRangeException("index"); 43 } 44 } 45 } 46 public class Example 47 { 48 public static void Main(string[] args) 49 { 50 Employee emp = new Employee("姓","某某"); 51 Console.WriteLine("{0}{1},{2}", emp[0], emp[1], emp[0,1]); 52 //显示 姓某某,姓某某 53 54 Console.ReadKey(); 55 } 56 }
三、运算符
运算符重载允许你定义C#中运算符应该如何操作自定义类型的操作数 运算符重载只能用于类和结构
并不是所有的运算符都可以重载
可以重载的一元运算符: + –! ~ ++ – ture false
可以重载的二元运算符: + – * / % & | ^ << >> == != > < >= <=
3.1.声明 <修饰符> 类型 operator 要重载的运算符 (参数列表)
修饰符只能为public static和extren这3个并且必须包含public static这两个
类型表示用重载的运算符计算结果返回分类型
参数列表 一元运算符重载方法带一个单独的类或者结构类型的参数 二元运算符重载方法带两个参数其中一个必须为类或者结构类型
重载运算符必须是要操作的类或结构的成员
重载的运算符不能1.创建新运算符2.改变运算符的语法3.重定义运算符如何处理预定义类型4.改变运算符的优先级或者结合性
对于比较运算符必须成对出现 ==和!= >和< >=和<=(其中==和!=如果重载还应重载从Object类中继承的Equals()和GetHashCode()方法)
1 using System; 2 3 public class People 4 { 5 public int Age { get; set; } 6 public static bool operator > (People age1,People age2) 7 { 8 return (age1.Age > age2.Age); 9 } 10 public static bool operator <(People age1, People age2) 11 { 12 return (age1.Age < age2.Age); 13 } 14 public static bool operator >(People age, int i) 15 { 16 return (age.Age > i); 17 } 18 public static bool operator <(People age, int i) 19 { 20 return (age.Age < i); 21 } 22 } 23 public class Example 24 { 25 public static void Main(string[] args) 26 { 27 People my = new People(); 28 People myBrother = new People(); 29 my.Age = 16; 30 myBrother.Age = 23; 31 Enter(my); 32 CompareAge(my,myBrother); 33 34 Console.ReadKey(); 35 } 36 public static void Enter(People name) 37 { 38 if (name > 18) 39 Console.WriteLine("允许进入"); 40 else 41 Console.WriteLine("不允许进入"); 42 } 43 public static void CompareAge(People name1, People name2) 44 { 45 if (name1 > name2) 46 { 47 Console.WriteLine("我比你大"); 48 } 49 else 50 Console.WriteLine("我比你小"); 51 } 52 }
四、析构函数
析构函数执行在类的实例被销毁之前需要清理或者释放非托管资源的行为。每个类只能有一个析构函数,析构函数不能带参数也不能带访问修饰符,析构函数和类同名但前面带一个~做前缀, 析构函数只能对类的实例起作用因此没有静态析构函数,不能在代码中显式调用析构函数 它在垃圾收集过程中调用。
你无法知道析构函数会在什么时候调用而且也不能显式调用系统会在对象从托管堆中移走前的某点调用他。这就决定了如果你的代码中包含需要及时清理的非托管资源别把它留给析构函数处理因为不能保证立即执行此时需要采用标准模式。
在类中实现IDisposable的接口,接口把资源清理的代码封装在一个void型的无参数方法中该方法应该叫Dispose 当你使用完资源并想释放他们时需要调用Dispose。
在编写接口中的Dispose方法时:以一种方式实现Dispose中的代码使该方法被不止一次调用也是安全的,把Dispose方法写入析构函数如果因为某种原因你的代码没有调用Dispose你的析构函数应该调用他并释放资源,因为是Dispose做清理而不是析构函数所以Dispose应当调用GC.SuppressFinalize方法该方法告诉CLR不要调用该方法的析构函数因为它已经被清理了