面向对象的三个基本特征分别为封装、继承、多态。
面向对象概述
对象是现实世界中的实体,它有三个基本要素,分别为封装、继承和多态。而类则是将具有相似属性和方法的对象集合起来。
1、面向过程
2、面向对象
3、类与对象的关系
类的继承
继承允许用户根据一个类来定义另一个类,这使得创建和维护应用程序变得更容易,同时也有利于重用代码和节省开发时间。
当创建一个类时,程序员不需要完全重新编写新的数据成员和成员函数,只需要设计一个新的类,继承已有类的成员即可。这个已有的类被称为基类,这个新的类被称为派生类。
基类和派生类
在类的继承中,被继承的类叫做基类或父类,继承的类叫做派生类或子类。一个类可以派生自多个类或接口,这意味着它可以从多个基类或接口继承数据和方法。
C#中创建派生类的语法如下:
<访问修饰符> class <基类>
{
...
}
clase <派生类>:<基类>
{
...
}
当一个类从另一个类派生出来时,派生类就自然具有基类的数据成员、属性和方法等。在基类中定义的这些成员,已经不需要在派生类定义中的重写。在派生类的定义中,只需编写基类所不具有的代码即可。
编写程序 ,通过图形的基类,派生出一个矩形的类,并计算出它的面积。
using System;
namespace Project1
{
class Shape //图形
{
public void setWidth(int w)
{
width = w;
}
public void setHeight(int h)
{
height = h;
}
protected int width;
protected int height;
}
class Rectangle : Shape //长方形继承于Shape
{
public int getArea()
{
return (width * height);
}
}
class Program
{
static void Main(string[] args)
{
Rectangle Rect = new Rectangle();
Rect.setWidth(6);
Rect.setHeight(8);
Console.WriteLine("总面积: {0}", Rect.getArea());
}
}
}
运行结果如下:
总面积: 48
继续的特性
继承是在类之间建立的一种相交关系,使得新定义的派生类的实例,可以继承已有的基类的特征,并且可以添加新的功能。下面是继承的一些特征。
(1)派生类只能继承一个基类,所以C#并不支持多重继承,但一个基类可以有多个直接派生类。
编写程序,通过继承 的方式,打印出一个公司里三位员工的自我介绍。
using System;
namespace Project2
{
class Clerk //创建一个职员的类
{
private string _name; //定义职员姓名的字段
public string Name //定义职员姓名的属性
{
get
{ return _name; }
set
{ _name = value; }
}
private string _department; //定义职员部门的字段
public string Department //定义职员部门的属性
{
get
{ return _department; }
set
{ _department = value; }
}
public void CSeyHello()
{
Console.WriteLine("大家好,我是{0}的{1}", Department, Name);
}
}
class Sales : Clerk //创建销售类Sales,继承于Clerk
{
private int _salesTarget; //定义职员销售目标的字段
public int SalesTarget //定义职员销售目标的属性
{
get
{ return _salesTarget; }
set
{ _salesTarget = value; }
}
public void SSayHello()
{
Console.WriteLine("大家好,我是{0}的{1},我的销售目标是{2}元", Department, Name, SalesTarget);
}
}
//创建技术支持类TechnicalSupport,继承于Clerk
class TechnicalSupport : Clerk
{
private double _satisfactionRate; //定义职员服务满意度的字段
public double SatisfactionRate //定义职员服务满意度的属性
{
get
{ return _satisfactionRate; }
set
{ _satisfactionRate = value; }
}
public void TSSayHello()
{
Console.WriteLine("大家好,我是{0}的{1},我的服务满意率为{2}分", Department, Name, SatisfactionRate);
}
}
class Program
{
static void Main(string[] args)
{
Clerk zs = new Clerk();
zs.Name = "张三";
zs.Department = "人力部";
zs.CSeyHello();
Sales ls = new Sales();
ls.Name = "李四";
ls.Department = "销售部";
ls.SalesTarget = 5000;
ls.SSayHello();
TechnicalSupport zh = new TechnicalSupport();
zh.Name = "周红";
zh.Department = "技术支持部";
zh.SatisfactionRate = 9.8;
zh.TSSayHello();
}
}
}
(2)继承是可以传递的。
(3)如果签名相同的方法在基类和派生类中都进行了声明,但该方法没有分别声明为virtual和override,派生类方法就会隐藏基类方法
编写程序,在派生类中隐藏和基类同名的方法。
using System;
namespace Project3
{
class myClass1
{
public void Write()
{
Console.WriteLine("这里是基类");
}
}
class myClass2 : myClass1
{
public new void Write() //隐藏方法
{
Console.WriteLine("这里是派生类");
}
}
class Program
{
static void Main(string[] args)
{
myClass2 b = new myClass2();
myClass1 a = b;
a.Write();
b.Write();
}
}
}
调用基类的构造函数
从所周知,基类的初始化工作由基类的构造函数完成,派生类的初始化工作则由派生类的构造函数完成,但是这样就产生了派生类构造函数的执行顺序问题。
1、当基类中没有定义构造函数时,派生类会默认地调用基类的默认构造函数。
2、当基类中编写一个有参构造函数后,再实例化派生类的对象,程序就会调用基类中无参的构造函数,如果该函数存在就调用它,否则编译器就会发出错误警告。因此,不论程序调用派生类中的哪个构造函数,都是在寻找基类中无参的构造函数,如果没有则报错,而非参数匹配。
3、基类中编写了构造函数,可以通过base关键字,指定创建派生类实例时应调用的基类构造函数。
编写程序,在派生类中可以指定调用基类的某个构造函数。
using System;
namespace Project4
{
public class myClass1 //基类
{
int Number;
public myClass1() //构造函数
{
Console.WriteLine("派生类调用基类的第一个构造函数");
}
public myClass1(int a)
{
Number = a;
Console.WriteLine("派生类调用基类的第二个构造函数");
}
public int GetNumber()
{
return Number;
}
}
public class myClass2 : myClass1 //派生类
{
//这个构造函数调用第一个基类的构造函数
public myClass2() : base() //派生类的构造函数
{
}
//这个构造函数调用第二个基类的构造函数
public myClass2(int a) : base(a) //派生类的第二个构造函数
{
}
static void Main()
{
myClass2 bs1 = new myClass2();
myClass2 bs2 = new myClass2(1);
}
}
}
运行结果:
base关键字还可以用于从派生类中访问基类的成员。
using System;
namespace Project5
{
public class Person
{
protected string tel = "444-55-6666";
protected string name = "张三";
public virtual void GetInfo()
{
Console.WriteLine("姓名: {0}", name);
Console.WriteLine("电话号码: {0}", tel);
}
}
class Employee : Person
{
public string id = "ABC567EFG";
public override void GetInfo()
{
base.GetInfo(); // 调用基类Getinfo方法
Console.WriteLine("学号: {0}", id);
}
}
class Program
{
static void Main()
{
Employee E = new Employee();
E.GetInfo();
}
}
}
运行结果 :
类的封装
寺是实现面向对象程序设计的第一步,封闭就是将数据或方法等集合在一个个的单元中。被封装的对象通常被称为抽象数据类型。抽象和封闭都是面向对象程序设计的相关特性。抽象允许相关信息可视化,封装则使开发者实现所需级别的抽象。
C#封装根据具体的需要,设置使用者的访问权限,并通过访问修饰符来实现。一个访问修饰符定义了一个类成员的范围和可见性。
public公开的
public允许一个类将其成员变量和成员函数暴露给其他的函数和对象。任何公有成员都可以被外部的类访问。
编写程序,访问类中的公有成员。
using System;
namespace Project6
{
class myClass
{
// 公有成员
public int length;
public int width;
public int getArea()
{
return length * width;
}
public void Display()
{
Console.WriteLine("长度: {0}", length);
Console.WriteLine("宽度: {0}", width);
Console.WriteLine("面积: {0}", getArea());
}
}
class Program
{
static void Main(string[] args)
{
myClass r = new myClass();
r.length = 6;
r.width = 3;
r.Display();
}
}
}
private私有的
private访问修饰符允许一个类将其成员变量和成员函数对其他的函数和对象进行隐藏。只有同一个类中的函数可以访问它的私有成员。即使是类的实例也不能访问它的私有成员。
编写程序,将一个类中的成员变量设置为私有成员。
using System;
namespace Project7
{
class myClass
{
private int length;
private int width;
public void Import()
{
Console.WriteLine("请输入长度:");
length = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("请输入宽度:");
width = Convert.ToInt32(Console.ReadLine());
}
public int getArea()
{
return length * width;
}
public void Display()
{
Console.WriteLine("长度: {0}", length);
Console.WriteLine("宽度: {0}", width);
Console.WriteLine("面积: {0}", getArea());
}
}
class Program
{
static void Main(string[] args)
{
myClass r = new myClass();
r.Import();
r.Display();
}
}
}
protected受保护的
protected访问修饰符允许派生类访问它的基类的成员变量和成员函数,这样有助于实现继承。
编写程序,通过派生类访问基类的受保护成员。
using System;
namespace Project8
{
class Point
{
protected int x;
protected int y;
}
class Program:Point
{
static void Main(string[] args)
{
Program dpoint = new Program();
// 直接访问受保护的成员
dpoint.x = 10;
dpoint.y = 15;
Console.WriteLine("x = {0}, y = {1}", dpoint.x, dpoint.y);
}
}
}
internal内部访问
internal的英文含义是“内部的”,而这个“内部的”的确切含义,是指“同一程序集”的内部,而非“同一命名空间”的内部。也就是说,internal修饰的方法或属性,只要是在同一个程序集中的类都可以访问,如果二者不在同一命名空间,只要使用using引用上相应的命名空间即可。
编写程序,将类中的成员用internal关键字修饰。
using System;
namespace Project9
{
class myClass
{
internal int length;
internal int width;
int getArea()
{
return length * width;
}
public void Display()
{
Console.WriteLine("长度: {0}", length);
Console.WriteLine("宽度: {0}", width);
Console.WriteLine("面积: {0}", getArea());
}
}
class Program
{
static void Main(string[] args)
{
myClass r = new myClass();
r.length = 15;
r.width = 8;
r.Display();
}
}
}
封装的意义在于保护或者防止代码(数据)被用户无意中破坏,防止对实现细节的访问。用户只提供调用类的方法,而调用者不必了解类内部怎样处理相关数据。
多态
通过继承实现的不同对象调用相同的方法,表现出不同的行为,称为多态。C#中的每种类型都是多态的。当派生类从基类继承时,它会获得基类的所有方法、字段、属性和事件。面向对象的语言使用虚方法表达多态
编写程序,表现出一个动物类吃方法的多种形态。
using System;
namespace Project10
{
public class Animal
{
public virtual void Eat() //定义一个吃的虚方法
{
Console.WriteLine("我是一只小动物,我喜欢吃肉");
}
}
//创建子类Cat后,继承父类Animal,重写父类Animal中的虚方法Eat
public class Cat : Animal
{
public override void Eat() //重写虚方法
{
Console.WriteLine("我是一只可爱的小猫,我喜欢吃鱼");
}
}
//创建子类Dog后,继承父类Animal,重写父类Animal中的虚方法Eat
public class Dog : Animal
{
public override void Eat()//重写虚方法
{
Console.WriteLine("我是一只强壮的大狗,我喜欢啃骨头");
}
}
//创建子类Mouse后,继承父类Animal,重写父类Animal中的虚方法Eat
public class Mouse : Animal
{
public override void Eat()//重写虚方法
{
Console.WriteLine("我是一只灰色的小老鼠,我喜欢吃粮食");
}
}
class Program
{
static void Main(string[] args)
{
//创建一个Animal基类数组,添加基类Animal对象,Cat对象,Dog对象,Mouse对象
Animal[] animals = {
new Animal(),
new Cat(),
new Dog(),
new Mouse()
};
//遍历一下animals数组
foreach (Animal animal in animals)
{
animal.Eat();
}
}
}
}
运行结果如下:
接口
接口简单理解就是一种约定,使得实现接口的类或结构 在形式上保持一致。
接口的声明
接口描述的是可属于任何类或结构的一组相关功能,所以实现接口的类或结构必须实现接口定义中指定的接口成员。接口使用interface关键字进行定义,可由方法、属性、事件、索引器或这四种成员类型的任意组合构造。
接口使用interface关键字进行定义,可由方法、属性、事件、索引器或这四种成员类型的任意组合构成。
接口的声明格式:
修饰符 interface 接口名称
{
//成员;
}
注意:接口名称习惯以I开头,跟在I后的第一个字符也是大写的,结构一般以able结尾。
使用接口时,需要注意以下几点:
(1)接口中只能声明方法不能定义,也就是说声明的方法不能有方法体。
(2)接口不能包含常量、字段、运算符、实例构造函数、析构函数或类型,不能包含静态成员。由于不能有字段,所以属性经常被写做自动属性。
(3)接口成员都是自动公开的,且不能包含任何访问修饰符。因为接口中的方法是用来定义对象之间通信的契约,所以指定接口中的方法为私有或保护没有意义。它们默认为公有方法。
(4)接口自身可从多个接口继承,类和结构可继承多个接口,但接口不能继承类。
接口的实现
接口类似于抽象基类,不能直接实例化接口;接口中的方法都是抽象方法,实现接口的任何非抽象类型都必须实现接口的所有成员。
实现接口的方式有以下两种:
(1)当隐式实现该接口的方法时,即可用接口调用方法,也可用具体类调用方法,但是实现的成员必须是公有的。
编写程序,隐式实现接口的方法。
using System;
namespace Project11
{
interface IProgram
{
void Write();
}
class Program : IProgram
{
void IProgram.Write()//显式实现接口成员
{
Console.WriteLine("显式实现接口的方法");
}
static void Main(string[] args)
{
Console.WriteLine("实例化类的对象调用接口方法:");
Program pro = new Program();
IProgram p1 = pro; //使用派生类对象实例化接口
p1.Write();
Console.WriteLine("声明接口实例调用接口方法:");
IProgram p2 = new Program(); //声明一个接口实例,但不是对接口进行实例化
p2.Write();
}
}
}
运行结果:
(2)当显示实现该接口的方法时,不能用访问修饰符public,并且必须显式指定接口名称。实现的成员不能通过类实例访问,只能通过接口实例访问。
编写程序,显示实现接口的方法。
using System;
namespace Project12
{
interface IProgram
{
void Write();
}
class Program : IProgram
{
public void Write()
{
Console.WriteLine("隐式实现接口的方法");
}
static void Main(string[] args)
{
IProgram p = new Program(); //也可以通过接口实例访问
p.Write();
Program pro = new Program(); //实现的成员可以通过类实例访问
pro.Write();
}
}
}
运行结果:
实现接口可以显式实现和隐式实现,那么这两种实现到底有什么优缺点呢?一般情况下,当类或者结构 要实现的是单个接口,可以使用隐形实现。如果类或者结构继承了多个接口且接口中具有相同名称成员时,就要用到显式实现。
编写程序,实现两个接口中同名但是具有不同功能的方法。
using System;
namespace Project13
{
interface ImyInterface1
{
int add();
}
interface ImyInterface2
{
int add();
}
class myClass : ImyInterface1, ImyInterface2
{
int ImyInterface1.add() //显式接口的成员实现
{
int a = 4;
int b = 6;
return a + b;
}
int ImyInterface2.add() //显式接口的成员实现
{
int a = 4;
int b = 6;
int c = 10;
return a + b + c;
}
}
class Program
{
static void Main(string[] args)
{
myClass myclass = new myClass();//实例化继承类的对象,该类继承了接口
ImyInterface1 imy1 = myclass; //使用继承接口类的对象 实例化接口
Console.WriteLine(imy1.add());//使用接口对象调用接口中的方法
ImyInterface2 imy2 = myclass; //使用继承接口类的对象实例化接口
Console.WriteLine(imy2.add());//使用接口调用方法
}
}
}
运行结果:
接口的继承
接口继承和类继承不同。首先,类继承不仅是说明继承,同时也是实现继承;而接口继承只是说明继承。也就是说,派生类可以继承基类的方法实现,而派生的接口只继承了父接口的成员方法说明,而没有继承父接口的实现,其次,C#中类继承只允许单继承,但是接口继承允许多继承 ,一个子接口可以有多个父接口。
接口可以从零或多个接口中继承。从多个接口中继承时,用冒号“:”后跟被继承的接口名字,多个接口名之间用逗号“,”分隔。
编写程序,声明三个接口分别IPeople、ITeacher、IStudent,其中,后两者继承于IPeople,然后Program类继承这三个接口,并且实现三个接口中的所有属性和方法。
using System;
namespace Project14
{
interface IPeople
{
//由于不能有字段,所以属性经常被写作自动属性
string Name //声明Name属性
{
get;
set;
}
string Gender //声明Gender属性
{
get;
set;
}
}
interface ITeacher : IPeople
{
void teach();
}
interface IStudent : IPeople
{
void study();
}
class Program : IPeople, ITeacher, IStudent
{
string _name = "";
string _gender = "";
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
public string Gender
{
get
{
return _gender;
}
set
{
_gender = value;
}
}
public void teach()
{
Console.WriteLine("大家好,我叫{0},我是{1}生,我是一名老师", Name, Gender);
}
public void study()
{
Console.WriteLine("大家好,我叫{0},我是{1}生,我是一名学生", Name, Gender);
}
static void Main(string[] args)
{
ITeacher iteach = new Program();
iteach.Name = "张三";
iteach.Gender = "男";
iteach.teach();
IStudent istu = new Program();
istu.Name = "李四";
istu.Gender = "女";
istu.study();
}
}
}
运行结果:
如果一个接口继承其它接口,那么实现类或结构就需要实现所有接口的成员。
编写程序,让一个接口继承另一个接口。
using System;
namespace Project15
{
interface IFirst
{
void FirstMethod();
}
interface ISecond : IFirst
{
void SecondMethod();
}
class Program : ISecond
{
static void Main()
{
Program pro = new Program(); //实例化类的对象
IFirst first = pro; //使用派生类对象实例化接口IFirst
ISecond second = pro; //使用派生类对象实例化接口ISecond
first.FirstMethod();
second.SecondMethod();
}
public void FirstMethod()
{
Console.WriteLine("这里是第一个接口的方法");
}
public void SecondMethod()
{
Console.WriteLine("这里是第二个接口的方法");
}
}
}
运行结果:
抽象类和密封类
接口的存在可以使C#中存在多重继承,这样使程序结构更加合理。abstract关键字和sealed关键字分别实现了抽象和密封的定义,这两种方法使程序的设计更加严密。
抽象类和抽象方法
如果一个类不与具体的事物相联系,而只是表达一种抽象的概念,仅仅是作为其派生类的一个基类,这样的类就是抽象类,在抽象类中声明方法时,如果加上abstract时就是抽象方法。
1、抽象类的概述及声明
抽象类与非抽象类的主要区别:
(1)抽象类不能直接被实例化。
(2)抽象类中可以包含抽象成员,但非抽象类中不可以。
(3)抽象类不能被密封。
2、抽象方法的概述及声明
在抽象类中也可以使用关键字abstract定义抽象方法,要求所有派生的非抽象类都要重载实现抽象方法,引入抽象方法的原因在于抽象类本身是一种抽象概念,有的方法并不需要具体实现,而是留下来让派生类重载实现。
声明抽象方法时需注意:
(1)抽象方法必须声明在抽象类中。
(2)声明抽象方法时,不能使用virtual、static和private修饰符。
(3)抽象方法声明引入一个新的方法,但是不提供具体的实现。
(4)当从抽象类中派生一个非抽象类时,需要在非抽象类中重写所有抽象类中的方法,提供具体实现细节,重写抽象方法时候使用override关键字。
编写程序,将图形声明成抽象类,并进行扩展。
using System;
namespace Project16
{
//定义基类Shape
public abstract class Shape //声明一个抽象类的图形
{
//protected的作用:允许派生类访问它的基类成员
protected string Color; //表示图形的颜色
//构造函数的作用帮助用户初始化对象,也就是给对象的每个属性依次赋值
public Shape(string Color) //定义带参的构造函数,为图形的颜色赋值
{
this.Color = Color;
}
public string GetColor() //获取图形颜色的方法GetColor
{
return Color;
}
public abstract double GetArea(); //表示图形的面积
//(1) 抽象方法必须声明在抽象类中。
//(2) 声明抽象方法时,不能使用virtual、static和private修饰符。
//(3) 抽象方法声明引入一个新的方法,但是不提供具体的实现。
}
//定义Cirle类,从Shape类中派生
public class Circle : Shape //圆形
{
//private的作用:只有在同一类中的方法,才能够去访问该变量
private double Radius; //圆的半径
//通过base关键字,指定创建派生类实例时应调用的基类构造函数。
public Circle(string Color, double Radius) : base(Color)
{
this.Color = Color;
this.Radius = Radius;
}
//当从抽象类中派生中一个非抽象类时,需要使用override关键字,来实现非抽象类中的方法
public override double GetArea() //重写抽象方法
{
return System.Math.PI * Radius * Radius;
}
}
//派生类Rectangular,从Shape类中派生
public class Retangular : Shape //矩形
{
protected double Length, Width; //声明长和宽
public Retangular(string Color, double Length, double Width) : base(Color) //构造函数
{
this.Color = Color;
this.Length = Length;
this.Width = Width;
}
public override double GetArea() //重写方法
{
return (Length * Width);
}
public double PerimeterIs() //周长
{
return (2 * (Length + Width));
}
}
//派生类Square,从Retangular类中派生
public class Square : Retangular //正方形
{
public Square(string Color, double Side) : base(Color, Side, Side) {; }
}
class Program
{
static void Main(string[] args)
{
Circle Cir = new Circle("黄色", 4.0);
Console.WriteLine("圆 形 的颜色是:{0},它的面积是:{1}", Cir.GetColor(), Cir.GetArea());
Retangular Rect = new Retangular("红色", 6.0, 8.0);
Console.WriteLine("矩 形 的颜色是:{0},它的面积是:{1},它的周长是:{2}",
Rect.GetColor(), Rect.GetArea(), Rect.PerimeterIs());
Square Squ = new Square("绿色", 5.0);
Console.WriteLine("正方形的颜色是:{0},它的面积是:{1},它的周长是:{2}",
Squ.GetColor(), Squ.GetArea(), Squ.PerimeterIs());
}
}
}
运行结果:
抽象类与接口的区别
1、抽象类
含有abstract修饰符的class即为抽象类,抽象类是特殊的类,只是不能被实例化,可以创建一个变量,其类型是一个抽象类,并让它指向具体子类的一个实例;除此以外,抽象类具有类的其他特性;重要的是抽象类可以包括抽象方法,这是普通类所不能的。抽象方法只能声明于抽象类中,且不包含任何实现,派生类必须覆盖它们。另外,抽象类可以派生自一个抽象类,可以覆盖基类的抽象方法也可以不覆盖。
2、接口
接口是引用类型,类似于类。接口和抽象类的相似之处有三点:
(1)不能实例化。
(2)包含未实现的方法声明。
(3)派生类必须实现未实现的方法,抽象类是抽象方法,接口则是所有成员(不仅是方法包括其他成员)。
接口具有如下特性:
接口除了可以包含方法之外,还可以包含属性、索引器、事件,而且这些成员都被定义为公有的。除此之外,不能包含任何其他的成员,例如常量、域、构造函数、析构函数、静态成员。一个类可以直接继承多个接口,但只能直接继承一个类(包括抽象类)。
3、两者的语法区别
(1)抽象类可以有构造方法,接口中不能有构造方法。
(2)抽象类中可以有普通成员变量,接口中没有普通成员变量。
(3)抽象类中的抽象方法的访问类型可以是public、protected,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
(4)抽象类中可以包含静态方法,接口中不能包含静态方法。
(5)一个类可以实现多个接口,但只能继承一个抽象类。
(6)抽象类实现的具体方法默认为虚方法,但实现接口的类中的接口方法却默认为非虚方法,当然用户也可以声明为虚方法。
密封类和密封方法
如果所有类都可以被继承,那么很容易导致继承的滥用,进而使类的层次结构体系变得十分复杂 ,这样会使开发人员对类的理解和使用变得十分困难。为了避免滥用继承,C#中提出了密封类的概念。
1、密封类概述及声明
密封类可以用来限制扩展性,如果密封了某个类,则其它类不能从该类继承;如果密封了某个成员,则派生类不能重写该成员的实现。
密封类的声明格式为:
访问修饰符 sealed class 类名:基类或接口
{
//类体
}
说明:
(1)密封类不能作为基类被继承,但它可以继承别的类或接口。
(2)在密封类中不能声明受保护成员或虚成员,因为受保护成员只能从派生类进行访问,而虚成员只能在派生类中重写。
(3)由于密封类的不可继承性,密封类不能声明为抽象的,即sealed修饰符不能与abstract修饰符同时使用。
2、密封方法的概述及声明
并不是每个方法都可以声明为密封方法,密封方法只能用于对基类的虚方法进行实现,并提供具体的实现。所以,声明密封方法时,sealed修饰符总是和override修饰符同时使用。
密封方法的声明格式为:
访问修饰符 sealed override 方法名称 (参数列表)
{
//方法体
}
其中,访问修饰符、参数列表都是可选的。
编写程序,定义一个密封方法,并对它进行重写。
using System;
namespace Project17
{
public class MyClass1
{
public virtual void Write()
{
Console.WriteLine("这是一个未密封的方法");
}
}
public class MyClass2:MyClass1
{//继承之后需要对虚方法Write进行重写
public sealed override void Write()
{
Console.WriteLine("这是一个密封的方法");
}
}
public class MyClass3:MyClass2
{
//public override sealed void Write()
//{
// Console.WriteLine("重写密封方法");
//}
//继承成员“MyClass2.Write()”是密封的,所以无法进行重写
}
class Program
{
static void Main(string[] args)
{
MyClass2 myClass2 = new MyClass2();
myClass2.Write();
}
}
}
运行结果:
3、密封类与密封方法的使用
密封类除了不能被继承外,与非密封类的用法大致相同,而密封方法则必须通过重写基类中的虚方法来实现。
编写程序,通过密封类与密封方法输出用户的基本信息。
using System;
namespace Project18
{
public class MyClass1
{
public virtual void ShowInfo() //虚方法,用来显示信息
{
}
}
public sealed class MyClass2 : MyClass1 //密封类,继承 MyClass1
{
private string _id = "";
private string _name = "";
public string ID
{
get
{
return _id;
}
set
{
_id = value;
}
}
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
public sealed override void ShowInfo()
{
Console.WriteLine("我是{0},我的ID是{1}", Name, ID);
}
}
class Program
{
static void Main(string[] args)
{
MyClass2 myclass2 = new MyClass2();
myclass2.ID = "BH0001";
myclass2.Name = "张三";
myclass2.ShowInfo();
}
}
}
运行结果
委托与事件
委托
从数据结构来讲,委托和类一样的,是一种用户自定义类型。委托是方法的抽象,它存储的就是一系列具有相同签名和返回类型的方法的地址。调用委托的时候,委托包含的所有方法将被执行。所有的委托都派生自System.Delegate类。
1、声明委托
委托是类型,就好像类也是类型一样。与类一样,委托类型必须在被用来创建变量以及类型对象之前声明。
委托的声明原型如下:
delegate <函数返回类型><委托名>(<函数参数>)
注意:委托可以依赖于一个类,也可以依赖于一个namespace命名空间,即在类里面声明和在类外面声明都可以。其中,delegate可以有返回类型,也可以没有返回类型。
2、委托的实例化
一旦声明了委托类型,委托对象必须使用new关键字来创建,且与一个特定的方法有关。当创建委托时,传递到new语句的参数就像方法调用一样书写,但是不带有参数。
委托实例化的原型如下:
<委托类型> <实例化名>=new <委托类型> (<注册方法>)
编写程序,实现MrLi委托MrZhang去帮助买车票的情景。
using System;
//声明一个委托,其实就是个“命令”
public delegate void MyDelegate();
namespace Project19
{
class MrZhang
{
public static void BuyTicket()
{
Console.WriteLine("小李又让小张去给他帮忙买车票!");
}
}
class MrLi
{
static void Main(string[] args)
{
//这里就是具体阐述这个命令是干什么的,本例是MrZhang.BuyTicket“小张买车票”
MyDelegate myDele = new MyDelegate(MrZhang.BuyTicket);
//这时候委托被附上了具体的方法
myDele();
}
}
}
运行结果:
3、委托的使用
编写程序,使用委托引用带有一个整形参数的方法,并返回两个整形值。
using System;
public delegate int NumberDelegate(int num1, int num2); // 委托,声明在类之外
namespace Project20
{
public class Program
{
public static int AddMethod(int num1, int num2)
{
Console.WriteLine("Sum={0}", num1 + num2);
return num1 + num2;
}
public static int SubMethod(int num1, int num2)
{
Console.WriteLine("Sub={0}", num1 - num2);
return num1 - num2;
}
public static void Main(string[] args)
{
NumberDelegate nd1 = AddMethod; //单播
Console.WriteLine("委托一个方法的结果为:{0}", nd1(3, 2));
NumberDelegate nd2 = SubMethod;
nd2 = nd1 + nd2; //多播
Console.WriteLine("委托两个方法的结果为:{0}", nd2(2, 4));
}
}
}
运行结果:
数据的值: 25
数据的值: 200
4、委托的多播
委托对象可使用“+”运算符进行合并。一个合并委托调用它所合并的两个委托。只有相同类型的委托可被合并。“-”运算符可用于从合并的委托中移除组件委托。
委托也可以包含多个方法,这种委托称为多播委托,也叫组播。
编写程序,实现委托的多播。
using System;
public delegate int NumberDelegate(int num1, int num2); // 委托,声明在类之外
namespace Project20
{
public class Program
{
public static int AddMethod(int num1, int num2)
{
Console.WriteLine("Sum={0}", num1 + num2);
return num1 + num2;
}
public static int SubMethod(int num1, int num2)
{
Console.WriteLine("Sub={0}", num1 - num2);
return num1 - num2;
}
public static void Main(string[] args)
{
NumberDelegate nd1 = AddMethod; //单播
Console.WriteLine("委托一个方法的结果为:{0}", nd1(3, 2));
NumberDelegate nd2 = SubMethod;
nd2 = nd1 + nd2; //多播
Console.WriteLine("委托两个方法的结果为:{0}", nd2(2, 4));
}
}
}
运行结果:
注意:对于多播,调用委托后,按照注意顺序执行。不过对于有返回值的方法,将返回最后注册的方法执行后的返回值。
事件
事件(Event)是C#中的一个高级概念,使用方法与委托相关。可以将事件编程分成两个部分来理解 ,分别为事件发生的类和事件接收处理的类。
1、通过事件使用委托
事件发生的类,就是说在这个类中触发了一个事件,但该类并不知道是哪一个对象或方法将会接收到并处理它触发的事件。所需要的是在发送方和接收方之间存在一个媒介。这个媒介在.NET Framework中就是委托(Delegate)。在事件接收处理的类中,用户需要有一个处理事件的方法。一般事件发生的类被称为发布器类,而接受并处理该事件的类,被称为订阅器类。
发布器(Publisher)是一个包含事件和委托定义的对象。事件和委托之间的联系也字义 在这个对象中。发布器类的对象调用这个事件,并通知其它的对象。
订阅器(Subscriber)是一个接受事件并提供事件处理程序的对象。在发布器类中的委托调用订阅器类中的方法(事件处理程序)。
2、C#中使用事件的步骤
(1)创建一个委托;
(2)将创建的委托与特定事件关联(.NET类库中的很多事件都是已经定制好的,所以排泄物不能就是相应的一个委托,在编写关联C#事件处理程序——也就是当有事件发生时要执行就去的时候需要和这个委托有相同的签名);
(3)编写C#事件处理程序;
(4)利用编写的C#事件处理程序生成一个委托实例;
(5)把这个委托实例添加到产生事件对象的事件列表中去,这个过程又叫订阅事件。
3、声明事件
声明事件时,发布者首先要定义委托,然后根据委托定义事件。
声明事件的语法如下:
<访问修饰符> event 委托名 事件名;
4、订阅事件
声明好事件之后,与该事件有关的人就会订阅事件。因此,只有订阅事件的对象才会收到发生事件的通知,没有订阅该事件的对象则不会收到通知。
订阅事件的语法如下:
事件名+= new 委托名(方法名);
事件的订阅通过“+=”操作符来实现,可以给事件加一个或多个方法委托。
5、引发事件
一般都是在满足某个事件下引发事件,如裁判员枪声一响,引发运动员奔跑这个事件。在编程中可以用条件语句,也可以使用方法引发事件。
奥运会上有一场短跑比赛,通过编写一个方法来让这场比赛开始。
using System;
namespace Project22
{
class Judgment
{
//定义一个委托
public delegate void DelegateRun();
//定义一个事件
public event DelegateRun EventRun;
//引发事件的方法
public void Begin()
{
EventRun();//被引发的事件
}
}
class RunSports
{
//定义事件处理方法
public void Run()
{
Console.WriteLine("运动员开始比赛");
}
}
class Program
{
static void Main(string[] args)
{
RunSports rs = new RunSports(); //实例化事件发布者
Judgment judgment = new Judgment();//实例化事件订阅者
//订阅事件
judgment.EventRun += new Judgment.DelegateRun(rs.Run);
//引发事件
judgment.Begin();
}
}
}
运行结果:
匿名方法与Lambda表达式
委托简化代码的编写量,但是通过匿名方法与Lambda表达式能够使代码更加精简。
匿名方法
C#为委托提供一种机制,可以为委托定义匿名方法,匿名方法没有名称,编译器会指定一个名称,匿名方法中不能使用跳转语句跳转到该匿名方法的外部,也不能跳转到该方法的内部。也不能在匿名方法外部使用ref和out参数。
注意:通过使用匿名方法,可以不必创建单独的方法,因此减少了实例化委托所需的编码系统开销。
1、编写匿名方法的语法
匿名方法是通过使用delegate关键字创建委托实例来声明的。
声明匿名方法的语法格式如下:
<委托类型> <实例化名>=delegate(<函数参数>){函数体};
2、匿名方法的使用
编写程序,通过匿名方法实现加法和乘法运算。
using System;
//声明委托
delegate void NumberChanger(int x, int y);
namespace Project23
{
class Program
{
static int num = 0;
//定义相加的方法AddMethod
public static void AddMethod(int a, int b)
{
num = a + b;
Console.WriteLine("相加方法: {0}", num);
}
//定义相乘的方法MultMethod
public static void MultMethod(int m, int n)
{
num = m * n;
Console.WriteLine("相乘方法: {0}", num);
}
static void Main(string[] args)
{
// 使用匿名方法创建委托实例
NumberChanger nc = delegate (int x, int y)
{
Console.WriteLine("匿名方法: x={0};y={1}", x, y);
};
// 使用匿名方法调用委托
nc(10, 15);
// 使用AddMethod方法实例化委托
nc = new NumberChanger(AddMethod);
// 使用匿名方法调用委托
nc(10, 15);
// 使用MultMethod方法实例化委托
nc = new NumberChanger(MultMethod);
// 使用匿名方法调用委托
nc(10, 15);
}
}
}
运行结果
匿名方法: x=10;y=15
相加方法: 25
相乘方法: 150
Lambda表达式
Lambda表达式是一种可用于创建委托类型的匿名函数。通过使用Lambda表达式,可以写入可作为参数传递或作为函数调用值返回的本地函数。Lambda表达式简化了开发中需要编写的代码量,对于编写LINQ查询表达式特别有用。
1、创建Lambda表达式
若要创建Lambda表达式,需要在Lambda运算符=>左侧指定输入参数,然后在另一侧输入表达式或语句块。
编写程序,创建Lambda表达式,计算一个整数的平方值。
using System;
namespace Project24
{
class Program
{
delegate int MyDele(int i); //声明委托
static void Main(string[] args)
{
MyDele DeleLambda = x => x * x; //指定名为x的参数,并返回x的平方值
int num = DeleLambda(4);
Console.WriteLine(num);
}
}
}
运行结果:
注意:“=>”运算符具有与赋值运算符“=”相同的优先级并且是右结合运算。
2、表达式Lambda
表达式位于=>运算符右侧的Lambda表达式称为“表达式Lambda”。表达式Lambda广泛用于表达式树的构造。表达式Lambda会返回表达式的结果,并采用以下基本形式:
(imput parameters) => expression
仅当Lambda只有一个输入参数时,括号才是可选的;否则括号是必需的。括号内的两个或更多输入参数使用逗号加以分隔:
(x, y) => x == y
有时,编译器难以或无法推断输入类型。如果出现这种情况,用户可以按以下实例中所示方式显示指定类型:
(int x, string s) => s.Length >x
输入参数类型必须全部为显示或全部为隐式;否则,C#将生成CS0748编译器错误。
使用空括号指定零个输入参数:
() => SomeMethod()
注意:表达式Lambda的主体可以包含一个方法调用。但是,如果 要创建在.NET Framework之外计算的表达式目录树,则不应在表达式Lambda中使用方法调用。
编写程序,使用表达式Lambda,计算两个整数的和。
using System;
namespace Project25
{
class Program
{
delegate int MyDele(int x, int y); //委托类型
static void Main()
{
MyDele DeleLambda = (x, y) => x + y;
int sum = DeleLambda(1, 1);
Console.WriteLine(sum);
}
}
}
运行结果: