【C#学习笔记01】C#抽象类、抽象方法、接口、密封类和密封方法、委托、匿名方法和事件
不行硅步无以至千里,学习虽苦,但终究会苦尽甘来,无需羡慕他人,无需成为他人,无需让自己变的所谓“更好”,好好做自己,更好的做自己就好!
凡所有相,皆是虚妄,若见诸相非相,便是如来!
1. 抽象类与抽象方法
抽象类和接口都包含可以由派生类继承的成员,它们都不能直接实例化,但是可以声明他们的变量,使用多态性把继承这两种类型的对象指定给他们的变量,通过这些变量来使用这些类型的成员【但不能直接访问派生类中的其他成员】,抽象类和接口的区别有以下几点:
- 抽象类的派生类只能继承一个基类,但是可以继承任意多个接口
- 抽象类中可以定义成员的实现,但接口中不可以;
- 抽象类中可以包含字段、构造函数、析构函数、静态成员或常量等,接口中不可以;
- 抽象类成员可以是私有的(非抽象)、受保护的、内部的或受保护的内部成员,接口中的成员必须是公共的。
说明:抽象类和接口这两种类型用于完全不同的目的,抽象类主要用作对象系列的基类,共享某些主要特称,例如共同的目的和接口。接口则主要用于类,这些类在基础水平上有所不同,但仍可以完成某些相同的任务。
1.1 抽象类
类的继承树中,越是在上方的类越抽象,在多态机制中,并不需要将弗雷初始化对象,我们需要的只是子类对象,C#中声明抽象类的的关键字是abstract,语法格式如下
访问修饰符 abstract class 基类:基类或接口
{
//类成员
}
使用abstract定义的类称为抽象类,使用这个关键字定义的方法称为抽象方法,抽象方法必须存在于抽象类中,在子类继承后,由子类实现

1.2 抽象方法
注意两点:
- 抽象方法必须声明在抽象类中
- 声明抽象方法是,不能使用virtual(虚拟)、static(静态)、private(私有)
当从轴向类派生一个非抽象类是,需要在非抽象类中重写抽象方法,以提供具体的实现,重写抽象方法使用override关键字
//声明一个抽象类,该抽象类中声明了一个抽象方法
public abstract class myClass
{
public abstract void method(); //抽象方法
}
2. 接口
由于C#不支持多重继承,但是客观世界出现多重继承的情况又比较多,因此提出了接口的概念,因此接口是抽象类的延申,通过接口可以实现多重继承的功能,接口具有如下特征:
- 接口类似于抽象基类,继承接口的任何非抽象类都必须实现接口的所有成员;
- 不能直接实例化接口
- 接口可以包含事件、索引器、方法和属性,但不能包含字段;
- 接口不包含方法的实现
- 类和接口可以从多个接口继承
- 接口自身可以从多个接口继承
单继承实例
//修饰符 interface 接口名称:继承的接口列表
//{
// 接口内容;
//}
//示例:声明一个接口,包含编号和姓名两个属性,还包含了一个自定义方法ShowInfo()
//接口定义
interface ImInterface
{
string ID
{
get;
set;
}
string Name
{
get;
set;
}
void ShowInfo();
}
//接口的实现与继承
internal class Program:ImInterface //从接口继承
{
string id = "";
string name = "";
public string ID
{
get
{
return id;
}
set
{
id = value;
}
}
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
public void ShowInfo()
{
Console.WriteLine("编号\t 姓名");
Console.WriteLine(ID + "\t" + Name);
}
static void Main(string[] args)
{
Program program = new Program(); //实例化Program类对象
ImInterface imInterface = program; //使用派生类对象实例化接口ImInterface
imInterface.ID = "01"; //为派生类中的ID属性赋值
imInterface.Name = "姓名"; //为派生类中Name赋值
imInterface.ShowInfo(); //调用派生类中的方法显示定义的属性
}
}
多重继承实例
interface IPeople
{
string Name { get; set; }
string Sex { get; set; }
}
interface ITeacher : IPeople //继承公共接口
{
void teach(); //教学方法
}
interface IStudent:IPeople //继承公共接口
{
void study(); //学习方法
}
internal class Program:IPeople, ITeacher, IStudent //多重继承,各接口用逗号隔开
{
string name = "";
string sex = "";
public string Name { get { return name; } set { name = value; } }
public string Sex { get { return sex; } set { sex = value; } }
public void teach()
{
Console.WriteLine(Name + " " + Sex + " 教师"); //教学方法
}
public void study()
{
Console.WriteLine(Name+" "+Sex+" 学生"); //学习方法
}
static void Main(string[] args)
{
Program program = new Program(); //实例化类对象
ITeacher iteacher = program; //使用派生类对象实例化接口iTeacher
iteacher.Name = "张三";
iteacher.Sex = "男";
iteacher.teach();
IStudent istudent = program; //使用派生类对象实例化接口iTeacher
istudent.study(); //此时调用Name、Sex是Program的Name、Sex即张三、男
istudent.Name = "李四";
istudent.Sex = "女";
istudent.study();
}
}
多重继承中,如果类实现了两个相同签名的成员或接口,可以显示的实现接口成员,避免接口实现不正确的情况,还是多重继承的例子,ITeacher、IStudent,定义了相同的成员today(),在派生类中进行显示的实现,需要注意的点:
- 显示接口成员实现中不能包含访问修饰符、abstract、virtual、override或static修饰符;
- 显示接口成员属于【接口】成员,因此不能使用类对象直接访问。
interface IPeople
{
string Name { get; set; }
string Sex { get; set; }
}
interface ITeacher : IPeople //继承公共接口
{
void teach(); //教学方法
void today();
}
interface IStudent:IPeople //继承公共接口
{
void study(); //学习方法
void today();
}
internal class Program:IPeople, ITeacher, IStudent
{
string name = "";
string sex = "";
public string Name { get { return name; } set { name = value; } }
public string Sex { get { return sex; } set { sex = value; } }
public void teach()
{
Console.WriteLine(Name + " " + Sex + " 教师"); //教学方法
}
public void study()
{
Console.WriteLine(Name+" "+Sex+" 学生"); //学习方法
}
void ITeacher.today() //显示接口成员实现
{
Console.WriteLine("我在上课");
}
void IStudent.today() //显示接口成员实现
{
Console.WriteLine("我在听课");
}
static void Main(string[] args)
{
Program program = new Program(); //实例化类对象
ITeacher iteacher = program; //使用派生类对象实例化接口iTeacher
iteacher.Name = "张三";
iteacher.Sex = "男";
iteacher.teach(); //使用接口对象调用派生类实现的方法
iteacher.today(); //使用接口对象调用派生类实现的方法
IStudent istudent = program; //使用派生类对象实例化接口iTeacher
istudent.study(); //此时调用Name、Sex是Program的Name、Sex即张三、男
istudent.Name = "李四";
istudent.Sex = "女";
istudent.study(); //使用接口对象调用派生类实现的方法
istudent.today(); //使用接口对象调用派生类实现的方法
}
}
3. 密封类和密封方法
3.1 密封类
继承滥用会导致类的层次结构体系变得十分复杂,这样使得使用任意对类的理解和使用变得十分困难。为了避免滥用,C#提出了密封类的概念,一旦密封,就不能再被继承。
使用密封类时应当满足的场景
- 类包含带有安全敏感信息的继承受保护成员
- 类继承多个虚成员,并且密封每个成员的开发和测试开销明显大于密封整个类
- 类是一个要求使用反射进行快速搜索的属性。密封属性可以提高反射在检索属性时的性能
密封类关键字seealed,使用密封类要注意的点
- 密封类可以基层别的类或接口,但不能被继承
- 密封类中不能声明受保护成员或虚成员,因为受保护成员只能从派生类访问,虚成员只能在派生类中重写,而密封类不能被派生;
- 密封类不能声明为抽象的,即sealed修饰符不能与abstract修饰符同时使用,原因同上
密封类实例
public sealed class oneClass
{
public int i = 0;
public void method()
{
Console.WriteLine("这是一个密封类");
}
}
3.2 密封方法
并非所有方法都可以被声明为密封方法,密封方法只能用于对基类的虚方法进行实现,所以声明密封方法,sealed和override修饰符同时使用
密封方法实例
public class OneClass
{
public virtual void Method()
{
Console.WriteLine("基类中的虚方法");
}
}
public class SonClass : OneClass
{
public sealed override void Method()
{
base.Method(); //调用基类中的Method()方法,
//base关键字为派生类调用基类成员提供了一种简写的方法
Console.WriteLine("密封类中重写后的方法");
}
}
internal class Program
{
static void Main(string[] args)
{
SonClass sonClass = new SonClass();
OneClass oneClass = sonClass; //用派生类对象实例话基类
oneClass.Method();
sonClass.Method();
}
}
密封类与密封方法实例
public class OneClass
{
public virtual void ShowInfo() { } //虚方法,用来显示信息
}
public sealed class SonClass : OneClass
{
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() //密封并重写基类中的ShowInfo()方法
{
Console.WriteLine(ID + " " + Name);
}
}
internal class Program
{
static void Main(string[] args)
{
SonClass sonClass = new SonClass(); //实例化密封类对象
sonClass.ID = "001"; //为密封类中的编号属性赋值
sonClass.Name = "张三"; //为密封类中的名称属性赋值
sonClass.ShowInfo(); //调用密封类中的密封方法
}
}
4.委托和匿名方法
委托时一种引用方法的类型,也就是方法的引用,或者可以理解为方法的别名,一旦为委托分配了方法,委托将与该方法具有完全相同的行为
4.1 委托
C#中的委托(Delegate)是一种引用类型,即在委托的内部包含一个指向某个方法的指针,通过使用委托,把方法的引用封装在委托对象中,然后将委托对象传递给调用引用方法的代码。
委托实例
public class TestClass
{
public int Add(int x,int y)
{
return x + y;
}
}
internal class Program
{
public delegate int OneDelegate(int x, int y); //定义一个委托类型OneDelegate
static void Main(string[] args)
{
TestClass testClass = new TestClass();
OneDelegate oneDelegate=testClass.Add; //创建委托类型的实例oneDelegate,并绑定到Add方法;
int intSum = oneDelegate.Invoke(2, 3); //委托的调用,等同于int intSum = oneDelegate(2, 3);
Console.WriteLine("运算的结果是"+intSum.ToString());
Console.ReadLine();
}
}
4.2 匿名方法
匿名方法是为了简化委托的可操作性,匿名方法允许一个与委托关联的代码被内联地写入使用委托的位置。除了这种便利之外,匿名方法还共享了对本地语句包含的函数成员的访问,匿名方法的语法格式:
delegate(【参数列表】)
{
【代码块】
}
匿名方法使用实例
delegate void DelOutPut(string s); //自定义委托类型
internal class Program
{
static void OutPutString(string s)
{
Console.WriteLine(s);
}
static void Main(string[] args)
{
//委托的引用指向匿名方法delegate(string j){}
DelOutPut del = delegate (string s)
{
Console.WriteLine(s);
};
del.Invoke("匿名方法被调用"); //委托对象del调用匿名方法
//del("匿名方法被调用"); //委托也可以使用这种方式调用匿名方法
Console.WriteLine("\n");
del = OutPutString; //委托绑定到命名方法OutPutString
del("命名方法被调用"); //委托对象del调用命名方法
Console.ReadLine();
}
}
5.事件
事件是指某个类的对象在运行过程中遇到的一些特定的事情,而这些特定的事情有必要通知给这个对象的使用者。
5.1 委托的发布和订阅
由于委托能够引用方法,而且能够链接和删除其他委托对象,因而就能够通过委托来实现事件的“发布和订阅”,通过委托实现事件处理的过程,通常需要以下四部
(1) 定义委托类型,并在发布者类中顶一个一个该类型的共有成员
(2) 在订阅者类中定义委托处理方法
(3) 订阅者对象将其事件处理方法链接到发布者对象的委托成员(一个委托类型的引用)上
(4) 发布者在特定的情况下“激发”委托操作,从而自动调用订阅者对象的委托处理方法,整体流程如下

委托发布和订阅的实例
public delegate void RingEvent(int ringKind); //声明一个委托类型
public class SchoolRing //定义发布者类
{
public RingEvent OnBellSound; //公有委托类型实例
public void Jow(int ringKind) //成员方法
{
if(ringKind == 1 || ringKind == 2)
{
Console.WriteLine(ringKind == 1 ? "上课铃响了," : "下课铃想了,");
if(OnBellSound != null) //委托发布不为空,说明他已经订阅了具体的方法
{
OnBellSound(ringKind); //执行委托实例绑定的方法
}
}
else
{
Console.WriteLine("这个铃声不正确!");
}
}
}
public class Students //定义委托订阅者类
{
public void SubscribeToRing(SchoolRing schoolRing) //定义委托事件的方法委托发布者类实例作为入参
{
schoolRing.OnBellSound += SchoolJow; //调用委托发布类实例,绑定事件处理方法
}
public void SchoolJow(int ringKind) //绑定的事件处理方法
{
if(ringKind == 2) //打下课铃
{
Console.WriteLine("同学们开始课间休息");
}
else if(ringKind == 1)
{
Console.WriteLine("同学们开始认真学习!"); //打上课铃
}
}
}
internal class Program
{
static void Main(string[] args)
{
SchoolRing schoolRing = new SchoolRing(); //创建事件发布者实例
Students students = new Students(); //创建时间订阅者实例
students.SubscribeToRing(schoolRing); //学生订阅学校铃声
Console.WriteLine("请输入打铃参数(1:表示打上课铃;2:表示打下课铃):");
schoolRing.Jow(Convert.ToInt32(Console.ReadLine())); //开始打铃动作
Console.ReadLine();
}
}
委托发布和订阅的缺点,外部对象可以通过“=”任意修改已经发布的委托,对委托的安全性造成严重的威胁,所以有了接下来的内容。
5.2 事件的发布和订阅
为了解决委托发布和订阅的缺点,C#提供了专门的事件处理机制,保障事件订阅的可靠性,作坊很简单,在发布委托的定义中加上event关键字:
public event RingEvent OnBellSound; //公有委托类型实例
通过这个修改,其他类型再使用OnBellSound委托时,就只能将其放在符合赋值运算符“+=”或“-=”的左侧。
5.3 EventHandler类
由于在事件发布和订阅的过程中,委托类型是一件重复性的工作,因此.NET类库中定义了一个EventHandler委托类型,并建议尽量使用该类型作为事件的委托类型。
该委托类型定义为:
pbulic delegate void EventHandler(object sender,EventArgs e);
object类型的参数:表示引发事件的对象,由于事件成员只能由类型本身(委托发布者类)出发,因此在触发时传递给该参数的值通常是this,例如5.1的例子中,可将ShoolRing类的OnBellSound事件定义为EventHandler委托类型,那么触发事件的diamond就是“OnBellSound(this,null);"
EventHandler类使用实例(修改5.1委托发布和订阅实例)
/// <summary>
/// 定义事件基类的派生类,用于处理委托发布者类发布的事件
/// </summary>
public class RingEventArgs : EventArgs
{
private int ringKind;
public int RingKind //返回铃声参数
{
get { return ringKind; }
}
public RingEventArgs(int ringKind) //在构造函数中初始化铃声
{
this.ringKind = ringKind;
}
}
/// <summary>
/// //定义发布者类
/// </summary>
public class SchoolRing
{
public EventHandler OnBellSound; //公有委托类型实例
//这里注意EventHander的定义是 public delegate void EventHandler(Object sender,EventArgs e)
//没有返回值,有两个参数
//参数1:sender表示引发事件的对象,由于事件成员只能有类本身触发,因此,在触发是传递该参数的值通常为this
//参数2:事件数据的基类,如果有事件数据,可以通过派生类对象传递参数
public void Jow(int ringKind) //成员方法
{
if (ringKind == 1 || ringKind == 2)
{
Console.WriteLine(ringKind == 1 ? "上课铃响了," : "下课铃想了,");
if (OnBellSound != null) //委托发布不为空,说明他已经订阅了具体的方法
{
OnBellSound(this, new RingEventArgs(ringKind)); //执行委托实例绑定的方法
}
}
else
{
Console.WriteLine("这个铃声不正确!");
}
}
}
/// <summary>
/// 定义委托订阅者类
/// </summary>
public class Students
{
public void SubscribeToRing(SchoolRing schoolRing) //定义委托事件的方法委托发布者类实例作为入参
{
schoolRing.OnBellSound += SchoolJow; //调用委托发布类实例,绑定事件处理方法
}
public void SchoolJow(object sender,EventArgs e)
{
if (((RingEventArgs)e).RingKind == 2) //将基类EventArgs类型参数还原为派生类RingEventArgs,并返获取RingKind
{
Console.WriteLine("同学们开始课间休息!");
}
else if(((RingEventArgs)e).RingKind ==1) //将基类EventArgs类型参数还原为派生类RingEventArgs,并返获取RingKind
{
Console.WriteLine("同学们开始认真学习!");
}
}
}
internal class Program
{
static void Main(string[] args)
{
SchoolRing schoolRing = new SchoolRing(); //创建事件发布者实例
Students students = new Students(); //创建时间订阅者实例
students.SubscribeToRing(schoolRing); //学生订阅学校铃声
Console.WriteLine("请输入打铃参数(1:表示打上课铃;2:表示打下课铃):");
schoolRing.Jow(Convert.ToInt32(Console.ReadLine())); //开始打铃动作
Console.ReadLine();
}
}
5.4 Windows事件
事件在Windows这样的图形界面程序中有着非常广泛的应用,事件的响应是程序与用户交互的基础。用户的绝大多数操作,如移动鼠标、改变光标位置、选择惨淡命令等,都可以触发相关的控件的事件,例如Button的Click(单击)就是一个EventHandler类型的事件:
public event EventHandler Click;
public Form1()
{
InitializeComponent();
button1.Click += Button1_Click;
}
private void Button1_Click(object sender, EventArgs e)
{
this.Close();
}
更多推荐



所有评论(0)