多态
多态(polymorphism),是指计算机程序运行时,相同的消息可能会送给多个不同的类别之对象,而系统可依据对象所属类别,引发对应类别的方法,而有不同的行为。简单来说,所谓多态意指相同的消息给予不同的对象会引发不同的动作称之。
C#中主要通过虚方法、抽象方法和接口实现多态。
虚方法
当从父类中继承的时候,虚函数和被继承的函数具有相同的签名。但是在运行过程中,运行系统将根据对象的类型,自动地选择适当的具体实现运行。虚函数是面向对象编程实现多态的基本手段。在 C# 语言中, 对基类中的任何虚方法必须用 virtual
修饰, 而派生类中由基类继承而来的重载方法必须用 override
修饰。
重写:当一个子类继承一父类,而子类中的方法与父类中的方法的名称,参数个数、类型都完全一致时,就称子类中的这个方法重写了父类中的方法。
重载:一个类中的方法与另一个方法同名,但是参数表不同,这种方法称之为重载方法。
抽象方法和抽象类
抽象类被定义为永远不会也不能被实例化为具体的对象。它往往用于定义一种抽象上的概念,在类的继承关系中它往往被定义在较上层的位置。在程序设计的实践活动中,抽象类与接口存在类似的地方,即它更偏重于对共通的方法和属性进行规约。但与接口存在一个非常大的差异则在于,抽象类往往可以规约一个共同的方法和属性时提供一个对他们的实现。
以现实世界为例:”水果”可以算作一个抽象类,而”苹果”,”香蕉”则可以作为它的派生类。区别在于,”水果”是个概念,它不会有实例,但是”苹果”和”香蕉”则肯定会有实例。
一个抽象类可以包含抽象和非抽象方法,当一个类继承于抽象类,那么这个派生类必须实现所有的
的基类抽象方法。
抽象方法:指一些只有方法声明,而沒有具体方法体的方法。抽象方法一般存在於抽象類或界面中。
接口
通常可以用is
或as
运算符确定对象实现了哪些接口。
接口的引用方法
为了引用该接口,可以把对象类型强制转换为接口类型。
通常可以用is
或as
运算符确定对象实现了哪些接口。
使用is
的方法
这里is并不是比较相同,是所属范围的关系,比如,可以说,是水果,但≠水果。这里要特别注意。
foreach(Animal someAnimal in Zoo)
{
if(someAnimal is IHerbivore)
{
IHerbivore veggie = (IHebivore) someAnimal;
veggie.GatherFood;
}
}
使用as
方法
foreach(Animal someAnimal in Zoo)
{
IHerbivore veggie = someAnimal as IHerbivore;
if(veggie!=null)
{
veggie.EatPlant();
}
}
is
as
操作符
is
运行符不能重载,is
运行符只考虑引用转换、装箱转换和取消装箱转换。不考虑其它转换,如果用户定义转换。
在is
运算符的左侧不允许使用匿名方法。lambda
表达式属于例外。
is
运算符通常像下面这样使用:
if (myObject is Employee)
{
Employee myEmployee = (Employee)myObject;
}
在这段代码中,CLR实际会检查两次对象的类型。is运算符首先核实myObject是否兼容于Employee类型。如果是,那么在if语句内部执行转换型,CLR会再次核实myObject是否引用一个Employee。CLR的类型检查增加了安全性,但这样对性能造成一定影响。这是因为CLR首先必须判断变量(myObject)引用的对象的实际类型。然后,CLR必须遍历继承层次结构,用每个基类型去核对指定的类型(Employee)。由于这是一个相当常用的编程模式,所以C#专门提供了as运算符,目的就是简化这种代码写法,同时提升性能。
as:用于检查在兼容的引用类型之间执行某些类型的转换。
Employee myEmployee = myObject as Employee;
if (myEmployee != null)
{ }
在这段代码中,CLR核实myObject是否兼容于Employee类型;如果是,as会返回对同一个对象的一个非null的引用。如果myObject不兼容于Employee类型,as运算符会返回null。
注意:as运算符造成CLR只校验一次对象的类型。if语句只是检查myEmployee是否为null。这个检查的速度比校验对象的类型快得多。
as运算符的工作方式与强制类型转换一样,只是它永远不会抛出一个异常。相反,如果对象不能转换,结果就是null。所以,正确的做法是检查最终生成的一引用是否为null。如果企图直接使用最终生成的引用,会抛出一个System.NullReferenceException异常。以下代码对此进行了演示:
Object o = new Object(); //新建一个Object对象。
Employee e = o as Employee; //将o转型为一个Employee
e.ToString(); //访问e会抛出一个NullReferenceException异常
as运算符类似于强制转换操作。但是无法进行转换,则as返回null而非引发异常。
装箱和拆箱(目前仅需了解)
装箱和拆箱是值类型和引用类型之间相互转换是要执行的操作。
装箱:装箱在值类型向引用类型转换时发生;
拆箱:拆箱在引用类型向值类型转换时发生。
C# Timer控件
Timer控件:Timer控件只有绑定了Tick事件,和设置Enabled=True后才会自动计时,停止计时可以用Stop()控制,通过Stop()停止之后,如果想重新计时,可以用Start()方法来启动计时器。Timer控件和它所在的Form属于同一个线程。
Timer控件嵌入到用户组件中后,在WinForm中调用不可使用,貌似是Timer控件的bug,需进一步验证。
关于窗体
Program.cs
中
Application.Run(new Form1());
会将该窗体注册为主窗体,如果不Close()而只是Hide(),则会一直存在于内存中。
在使用C#进行Winform编程时,我们经常需要使用一个登录框来进行登录,一旦输入的用户名密码登录成功,这时登录窗口应该关闭,而且同时打开主程序窗口。该如何来实现呢?
乍一想,很简单啊,打开主窗口就用主窗口的Show()方法,而关闭登录窗口就用登录窗口的Close()方法即可。即代码如下:
//Program.cs中代码:
Application.Run(new FormLogin());
//登录窗口(FormLogin)代码:
private void button1_Click(object sender, EventArgs e)
{
if (textBox1.Text == "a") { //验证用户名密码
FormMain fm = New FormMain();
fm.Show(); //打开主窗口
this.Close(); //关闭登录窗口
}
}
事实证明,这种办法是行不通的。因为主窗口是由登录窗口打开的,所以我们在关闭登录窗口时,主窗口也会被一起关闭。这是一个线程树,或者窗口树的关系,即一个窗口关闭时,由它打开的新窗口都将被关闭。
所以,对于登录窗体和主窗体之间的问题,可以利用以下方式来解决:
////Program.cs中代码:
FormLogin fl = new FormLogin();
fl.ShowDialog();
if (fl.DialogResult == DialogResult.OK)
{
Application.Run(new FormMain());
}
else
{
return;
}
//登录窗口(FormLogin)代码:
private void button1_Click(object sender, EventArgs e)
{
if (textBox1.Text == "aaa") { //验证用户名密码成功
this.DialogResult = DialogResult.OK; //返回一个登录成功的对话框状态
this.Close(); //关闭登录窗口
}
}
获取(重写)上下左右键的点击事件
private void Form3_KeyDown(object sender, KeyEventArgs e)
{
//this.KeyPreview = true;
//处理启动窗体的 KeyPress 或 KeyDown 事件,将窗体的 KeyPreview 属性设置为 true,使键盘消息在到达窗体上的任何控件之前先被窗体接收。
this.Text = string.Format("e.KeyCode:{0} e.KeyData: {0} e.KeyValue: {1}", e.KeyCode, e.KeyData, e.KeyValue);
switch (e.KeyCode)
{
case Keys.W:
this.button1.Location = new Point(button1.Location.X, button1.Location.Y - 2); break;
case Keys.S:
this.button1.Location = new Point(button1.Location.X, button1.Location.Y + 2); break;
case Keys.A:
this.button1.Location = new Point(button1.Location.X - 2, button1.Location.Y); break;
case Keys.D:
this.button1.Location = new Point(button1.Location.X + 2, button1.Location.Y); break;
//方向键不能在KeyDown事件中被触发,以下代码无效。
//case Keys.Left:
// this.button1.Location = new Point(this.button1.Location.X - 2, this.button1.Location.Y); break;
//case Keys.Right:
// this.button1.Location = new Point(this.button1.Location.X + 2, this.button1.Location.Y); break;
default:
break;
}
}
protected override bool ProcessDialogKey(Keys keyData)
{
switch (keyData)
{
case Keys.Up: Move(0); break;
case Keys.Left: Move(1); break;
case Keys.Down: Move(2); break;
case Keys.Right: Move(3); break;
}
return true; //返回 true 以指示已处理该键。
//return base.ProcessDialogKey(keyData);
}
void Move(int i)
{
switch (i)
{
case 0: this.button1.Location = new Point(this.button1.Location.X, this.button1.Location.Y - 2); break;
case 1: this.button1.Location = new Point(this.button1.Location.X - 2, this.button1.Location.Y); break;
case 2: this.button1.Location = new Point(this.button1.Location.X, this.button1.Location.Y + 2); break;
case 3: this.button1.Location = new Point(this.button1.Location.X + 2, this.button1.Location.Y); break;
default: break;
}
}
在一个事件中调用另一个事件
//比如要在button_click事件中获取pictureBox1_click事件,可以在button_click事件中写入如下语句
pictureBox1_Click(pictureBox1, null);
播放声音
SoundPlayer player = new SoundPlayer(@"C:\Users\M0015\Desktop\pic\sound\7 B.wav");
player.Play();
可以用上述方式播放wav格式的声音文件。